Since the pseudo-threads library is non-preemptive, threads only lose control of the CPU when they explicitly block. You've seen that before. However, what does ``continuation-based'' mean? What it means is that whenever a thread makes a potentially blocking library call, that call will not return. Instead, all such calls require the thread to pass a pointer to a procedure and a (void *) pointer. This is called a ``continuation''. When the thread unblocks (or if the thread never was supposed to block), instead of returning to the calling procedure, the continuation is invoked -- the procedure is called with the (void *) pointer as its argument.
void *pt_fork(function, arg) void (*function)(); void *arg; pt_join(void *thread_id, function, arg) void *thread_id; void (*function)(); void *arg; pt_joinall(function, arg) void (*function)(); void *arg; pt_exit()pt_fork() creates a new thread, which will execute function(arg), and puts it on the ready queue. It returns the thread id of the thread (which is a (void *). Since the thread system is non-preemptive, the thread that calls pt_fork() does not lose the CPU after the call. The new thread will only execute once the calling thread blocks.
pt_join() is a blocking call. It blocks and waits for the thread thread_id to complete. When that thread completes, the thread calling pt_join() is put on the ready queue, and when it is executed, its continuation (function(arg)) is executed. pt_join never returns. When its continuation returns or calls pt_exit(), its thread is terminated.
pt_joinall() is also a blocking call. It waits until there are no more threads in the system that are either ready to run or sleeping. When that happens, its continuation is executed. Note that it will also run if all other threads are blocked on semaphores, because in such a case, there is no way for those threads to become ready. pt_joinall() is included because it is more convenient to use than pt_join() when you just want to block until all threads are done.
pt_exit() makes the thread exit. It is just like pthread_exit() in the pthreads package, except there is no return value. As in the pthreads package, in most cases, just calling return does the same thing, but sometimes you want to make sure. For example, if you don't want your main() program to return because that will exit the process, and you don't want to call pt_join() or pt_joinall(), then you can have it call pt_exit().
#includeThis is pretty simple. Note how pt_join calls exit(0) when the hw() thread returns. One important thing to note is that if the main() program returns, the process will exit, and no other threads will run. Thus, you have to make the main thread block in order to make the other threads run. In hw.c, this is done with the pt_join() call.#include "pt.h" extern exit(); hw() { printf("Hello World\n"); } main() { void *t; t = pt_fork(hw, NULL); pt_join(t, exit, 0); }
hw2.c is like hw.c except that it defines a procedure to call upon joining, and it prints out a few statements. See if you can trace how it gets its output:
UNIX> hw2 Main thread -- between pt_fork and pt_join Hello World In do_exit -- returning No more threads to run UNIX>Note that the printf("Main thread -- do I ever get here?\n"); line never gets executed. This is because the pt_join() call does not return. Instead, do_exit(NULL) is called, and when do_exit() returns, there are no more threads to run, so the system prints out "No more threads to run" and exits. If you do not exit using exit(), then the threads system will exit if there are no threads to run, and no threads sleeping. Before it exits, it prints "No more threads to run".
hw3.c forks off five hw() threads, and then calls pt_joinall() to exit when they are all done.
A: If the main() program exits without blocking, the process exits. To run the threads, the main() program must make a blocking call. Therefore, if you remove the pt_joinall() line in hw3.c, the program will exit without printing anything. Try it.
A: Now, the main() program blocks, so the thread system will indeed start running. The problem is that we're only calling pt_join() on the last thread (#4). We've lost the thread id's of the others during the for loop. So, once thread #4 finishes, the continuation exit(0) will be put on the ready queue. At that point, all the other threads will be done, so it will be executed, and the program will exit. In other words, the output will be the exact same as hw3.
A: Now, when thread #4 is done, the pt_join() call causes the continuation hw(10) to be put on the ready queue. As it will be the only thread, it will execute, and ``Thread 10: Hello World'' will print out. When hw(10) returns, there will be no more threads to run. Since no pt_joinall() continuation has been specified, the threads system will print out ``No more threads to run'' and the system will exit. The whole output will be:
Thread 0: Hello World Thread 1: Hello World Thread 2: Hello World Thread 3: Hello World Thread 4: Hello World Thread 10: Hello World No more threads to run
Pt_sleep() is the way you make a thread sleep in the pt-library without having the other threads sleep too.
pt_sleep(s, function, arg) int s; void (*function)(); void *arg;This specifies that after s seconds, the continuation function(arg) will be put on the end of the ready queue. If s is less than or equal to zero, this simply puts the continuation function(arg) at the end of the ready queue and runs the next thread. Note that pt_sleep() is a blocking call, and therefore does not return.
pt_yield() is equivalent to pt_sleep() where s is zero:
pt_yeild(function, arg) void (*function)(); void *arg;To test this out, look at sleeptest.c. This program forks off five threads. Each thread prints out its id, a counter, and how many seconds it is going to sleep. This is a random number between zero and 4. Then it calls pt_sleep() for that many seconds. When the thread is done sleeping, it calls thread(t) again -- thus it iterates forever printing, incrementing the counter and sleeping.
Try it out. Sample output is in sleeptest.out. It may be hard to figure out if everything is working right. To help out, grep to get the output of a single thread:
UNIX> grep 'Thread 0' sleeptest.out 0: Thread 0. i = 0. Sleeping for 0 seconds 0: Thread 0. i = 0. Sleeping for 1 seconds 1: Thread 0. i = 0. Sleeping for 1 seconds 2: Thread 0. i = 0. Sleeping for 0 seconds 2: Thread 0. i = 0. Sleeping for 0 seconds 2: Thread 0. i = 0. Sleeping for 4 seconds 6: Thread 0. i = 0. Sleeping for 2 seconds 8: Thread 0. i = 0. Sleeping for 0 seconds 8: Thread 0. i = 0. Sleeping for 1 seconds 9: Thread 0. i = 0. Sleeping for 0 seconds 9: Thread 0. i = 0. Sleeping for 4 seconds 13: Thread 0. i = 0. Sleeping for 1 seconds 14: Thread 0. i = 0. Sleeping for 3 seconds 17: Thread 0. i = 0. Sleeping for 2 seconds 19: Thread 0. i = 0. Sleeping for 3 seconds 23: Thread 0. i = 0. Sleeping for 3 seconds UNIX>You should see that everything looks good. Four seconds did pass when it slept for 3 seconds at the 19 second mark, but that's ok -- like Unix's sleep() call, this is a lower bound, not an upper bound.