Professor Norm Matloff
Dept. of Computer Science
University of California at Davis
Davis, CA 95616
(Please mail any questions to Norm Matloff.)
The GNU version of threads, referred to here as "pth", differs from POSIX threads, or "pthreads", in that pth is nonpreemptive; once a thread acquires the CPU, it will hold the CPU until voluntarily relinquishing it, either by calling a wait function such as pth_cond_await(), or by calling pth_yield().
There are various other thread packages which are nonpreemptive, but pth is the one which is most portable across Unix platforms.Advantages and Disadvantages of the Nonpreemptive Approach:
In my view, the principle advantages of the nonpreemptive approach is that it (a) makes for cleaner code, since fewer mutex operations are needed, and (b) makes for easier debugging, due to its deterministic operation. Note that point (a) makes it easier to keep various nonthreaded functions, e.g. random number generators, thread-safe. One down side is that it is probably easier to write code which results in deadlock (though again, the cause of deadlock is easier to track down if does occur). Also, the preemptive approach is needed if one wants true parallelism in a multiprocessor environment.Where to Get It:
Available at the GNU pth home page.Installation:
Installation is straightforward. Start by running "configure", etc. Be sure to use the --enable-pthread option with "configure".Library and Include-File Usage:
Of course, you need to include <pth.h>.
Link to pth's libpth.a, though pth's libpthread.a works too.Threads and States:
The parent program (executing the spawn) is the first thread.
The threads in state WAITING are those doing pth_cond_await(). The threads in state READY are those which are ready to run, including those which yielded. There is always exactly one thread in state RUNNING.
Pth uses actions such as setjmp() and longjump() for its main implementation device. By keeping this in mind, it will be easier to understand the flow of control from one thread to another, and thus easier to write and debug pth applications.
If you spawn a thread from within a C++ constructor function, make sure to invoke the class with "new", not by declaring a static global instance of the class.
The last argument to pth_spawn(), "arg", is somewhat misleadingly described in the documentation. The argument is passed "by value," not as a pointer to the actual argument, even though you must cast the argument to type (void *).
Ordinary I/O, e.g. read(), write(), accept(), etc. will block (or not) in the same way as in a nonthreaded program. If desired, pth offers pth_read(), etc., which make the thread yield control but does not make the program as a whole yield control of the CPU. The thread temporarily goes to WAITING state, and is awoken when the I/O operation completes.Thread-global variables:
You may wish to store some thread-specific information which will be "global" to that thread. To do this, use thread attributes. This involves (a) declaring an attribute t, of type pth_attr_t, (b) calling pth_attr_new(), assigning the return value to t, (c) calling pth_attr_set() on this attribute, with the second argument being PTH_ATTR_NAME and the third argument being a character string containing the thread-global variables, and finally (d) calling pth_spawn(), with the first argument being t.To retrieve the thread-global information, call pth_ctrl(PTH_CTRL_GETNAME,pth_self()) and assign it to a pointer to a character array. Debugging:
When in, say, gdb, at the beginning you may get error messages like
Program received signal SIGUSR1, User defined signal 1.
You will get one such error for each thread. Just ignore them (they occur because pth uses signals), and do a Continue operation in gdb. Or, issuing the following command to gdb will cause gdb to not stop when the signal occurs (though it will print a message):
handle SIGUSR1 nostop print pass
When you do single-step in gdb and hit a call to, say, pth_yield(), you probably will NOT see control jump to another thread; instead, you will likely end up in the pth internals. So, make sure you have put breakpoints at good places in your application threads (typically right after a line which relinquishes control of the CPU , and when you come to a call in which this thread will relinquish the CPU, just give gdb a Continue command instead of single-stepping.
The call pth_ctrl(PTH_CTRL_GETTHREADS) will return the total number of threads, while calls like pth_ctrl(PTH_CTRL_GETTHREADS_WAITING) will return the number of waiting threads, etc.
The call pth_ctrl(PTH_CTRL_DUMPSTATE,zzz); is very useful, writing the current state of all threads to the file whose file pointer is zzz, e.g.
FILE *zzz; ... zzz = fopen("yyy","w");
But better, do
FILE *zzz; ... zzz = fdopen(1,"w");Emulation for pthreads:
If compile, say z.c, do
cc z.c /usr/local/pth/lib/libpthread.a
Be sure to include <phtreads.h>.
I have used pth as the basis of my process-oriented discrete-event simulation package, psim.
A client/server paird, Clnt.C and Svr.C, illustrate the use of pth with I/O. See the comments at the top of Svr.C for details.
Other software Web sites by Norm Matloff: