Single thread: A program that creates and thread (which prints something out) and then waits for it to complete.If you are able to run these tests, you will certainly get a good deal of credit for the project (there will be a few more tests though).
Multiple threads: creates multiple threads and then waits for all of them to complete. The threads should be run in FIFO order. There may be a large number of these, too -- which could stress whether you clean up the thread stack when done.
Multiple threads + yield: creates multiple threads which frequently yield to each other. Testing whether yield works and FIFO ordering is preserved.
Multiple threads + lock: creates threads and has them call lock/unlock on a single lock around a critical section.
Multiple threads + lock + yield: creates threads, which grab lock then yield. Makes sure that lock is still held properly across yielding. Also makes sure that lock queue is managed in FIFO order.
Multiple threads + condition variable: creates two threads which use a condition variable to wait/signal each other.
Multiple threads + condition variable + yield: creates threads which use condition variables and locks to test whether condition variable queues are managed in FIFO order.
11/19:
Set the stack size of new threads to something between 256KB and 1MB (the exact number doesn't matter).
11/18:
You can assume virtually all of the tests will call thread_waitall() from the main thread after creating a bunch of threads. The main thread might call thread_yield, though, first. Other threads will not be calling thread_waitall(). The idea here is to make your life simpler -- if your library is more general and can handle these things, great.
11/18:
Do
NOT
use the PTHREADS library in any way in this assignment.
11/8:
The prototype for thread_create() has been clarified, and is also highlighted in
red.
11/7:
From discussion today:
Simple list
Basic context switching code
More advanced switching
Add yielding
11/4:
Note that this description is slightly different than what we covered in discussion class. The differences are highlighted in
red.
To learn how threads really workIn this project, you will write a library to support multiple threads within
To learn how to build a real (and fast?) mutex lock
To learn how those pesky condition variables really work
To learn how to build a shared library
You should create a header file that has the following routines within it;
you should call this file "537thread.h".
void thread_libinit()
thread_libinit is called to initialize the thread library.
thread_create is used to create a new thread. When the newly createdvoid thread_yield(void)
thread starts, it will call the function pointed to by func and pass it the
single argument arg.
thread_yield causes the current thread to yield the CPU to the next
runnable thread.
thread_lock, thread_unlock, thread_wait, and thread_signal implement locks
and condition variables. lock_t is a type you define in your thread
library and has all needed info to build a proper lock. cond_t is a type
you define and is used to implement condition variables. The associated
init functions do what is needed to init the mutex or condition variable.
when the main thread calls this, it should wait for all other
threads to finish. If all threads run to completion, thread_waitall()
should return 0. If threads still exist but none are runnable,
thread_waitall() should return -1.
gcc -c 537thread.c -g -Wall -fpic
The -c flag tells the compiler to create an object file (in this case,
537thread.o), the -g flag is good to have on when debugging (so you can use
the debugger gdb), the -Wall flag should ALWAYS be used, and finally the -fpic
flag tells the compiler to use something called "position-independent" code,
which is good to use when building shared libraries. We'll learn more about
what this means later; if you are curious, read the gcc info page for details.
Note that any other files that are going to be linked into the library should
be compiled in the same way.
Now that you have 537thread.o, you'll want to make a shared library out of
it. The way you do that is with the following line:
gcc -o lib537thread.so 537thread.o -shared
It's that easy!
gcc -o main main.c -L. -R. -l537thread
The -l537thread flag tells the compiler to look for a library called
lib537thread.so (or lib537thread.a, but don't worry about that), and the
-L. flags tells the compiler to look for the library in the "." directory
("." is a way to refer to the current direc tory in Unix). Finally, the
-R. flag tells the compiler to include information in the executable that
tells the program, when running, to look in "." to find the library.
You now know how to build a shared library. However, you may not (yet) know
how to build a thread library. We now discuss some of the key components.
Here
is an example of how these routines can be used:
When you run this, you get the following:
prompt> ./main2Read the man pages of getcontext(), makecontext(), and swapcontext()
start f2: 400
start f1: 300
finish f2: 400
finish f1: 300
prompt>
However, your thread library is NOT preemptive -- you should have
complete control over when a thread runs and hence a thread will
not be interrupted and context-switched arbitrarily to another
thread. Because of this, you do NOT need to use anything fancy like
compare-and-swap to implement locks. Think about why this is and
build your locks and condition variables accordingly.
All scheduling queues should be FIFO. This includes the ready queue and the
queue of threads waiting for a signal.
When a thread calls thread_create, the caller does not yield the CPU. The
newly created thread is put on the ready queue but is not executed right away.
When a thread calls thread_unlock, the caller does not yield the CPU. The
woken thread is put on the ready queue but is not executed right away.
When a thread calls thread_signal, the caller does not yield the CPU. A
woken thread is put on the ready queue but is not executed right away. A
woken thread should request the lock when it next runs.
When the main thread calls thread_waitall, it should give up the CPU
and the next runnable thread should run. If there are no threads remaining,
it should return 0. If there are no runnable threads, it should return -1.
Use assertion statements copiously in your thread library to check for
unexpected conditions. These error checks are essential in debugging
concurrent programs, because they help flag error conditions early.
Read the man page for assert() to see how these work.
Declare all internal variables and functions (those that are not called
by clients of the library) "static" to prevent naming conflicts with programs
that link with your thread library.
Your thread library must compile into a single dynamically-linked shared
library called "lib537thread.so", with an associated header file "537thread.h".