For example, you can tell npthread to call the handle0() function whenever data is available to be read on descriptor 0, and to call the handle6() function whenever data is available to be read on descriptor 6. If neither descriptor has data, npthread will wait until data arrives.
The npthread library allows the following seven types of threads:
The npthread library provides non-preemptive threads: specifically, a function must return before npthread calls another function. For example, say you've told npthread to call the handle0() function whenever data is available to be read on descriptor 0, and to call the handle6() function whenever data is available to be read on descriptor 6. If descriptors 0 and 6 both have data, npthread will call handle0() and then handle6(), or it might instead call handle6() and then handle0(); but it will not overlap handle0() and handle6().
Other thread libraries provide semi-preemptive threads: a function does not need to return before another function is called, but it does need to take some explicit action, such as calling a yield() function. (Semi-preemptive threads are sometimes confusingly called non-preemptive threads. The word coroutines can refer to either semi-preemptive threads or non-preemptive threads.)
Basic issues to consider in deciding whether to use a preemptive thread library, a semi-preemptive thread library, or a non-preemptive thread library:
The npthread library replaces my old sigsched library. Other non-preemptive thread libraries: Niels Provos's libevent, Dan Egnor's liboop, libwww's HTEvent, and GLib's Main Event Loop. Some semi-preemptive thread libraries: Keld Helsgaun's COROUTINE, Ralf Engelschall's Pth (formerly NPS), and Netscape's State Threads. The most common preemptive-thread interface is the POSIX thread (Pthread) interface supported by various libraries.
#include "npthread.h" int (*f)(int64); int64 n; npthread_add(f,n);npthread_add creates a new ``important'' thread that will call f(n) as soon as possible (after npthread_start is called). It returns 1 to indicate success.
If npthread_add runs out of memory, it returns 0 without changing the list of threads.
#include "npthread.h" int (*f)(int64); int64 n; int s; npthread_addsignal(f,n,s);npthread_addsignal creates a new ``unimportant'' thread that will call f(n) when UNIX signal s is received (after npthread_start is called). It returns 1 to indicate success.
If npthread_addsignal runs out of memory, it returns 0 without changing the list of threads.
UNIX signals handled by npthread are not an exception to the rule that one thread does not preempt another. For example, after
npthread_add(compute,0); npthread_addsignal(cleanup,0,SIGTERM); npthread_start();the function compute(0) will be called. If a SIGTERM signal arrives while compute(0) is running, it has no immediate effect; cleanup(0) will be called after compute(0) returns.
Do not attempt to use UNIX signals as counters. Two occurrences of a signal in quick succession will be combined into a single occurrence of the signal; a thread watching the signal will be called once, not twice.
#include "npthread.h" npthread_start(void);npthread_start looks for a thread whose call condition is satisfied, calls the thread function, and repeats, as long as there is at least one ``important'' thread left. It then returns 1.
A thread function is required to return one of the following numbers:
If npthread_start has trouble preparing internal resources, it returns 0, setting errno to indicate the error. A failure of this type cannot happen once npthread_start begins running threads.
npthread_start does not check every call condition every time it calls a thread function. Its main loop checks many call conditions and then calls many threads. Specifically:
These functions are for use solely in thread functions. They must not be called outside npthread_start.
#include "npthread.h" int (*f)(int64); int64 n; npthread_jump(f,n);npthread_jump tells the npthread library that f(n) is the function to be called by the current thread. npthread_jump is a jump, not a subroutine call: it wipes out the previous function pointer.
#include "npthread.h" npthread_unimportant(void); npthread_important(void);npthread_unimportant changes the current thread from ``important'' to ``unimportant.'' If the thread is already ``unimportant,'' npthread_unimportant leaves it alone.
npthread_important makes the opposite change.
#include "npthread.h" tai6464 t; npthread_sleepuntil(t);npthread_sleepuntil tells the npthread library to call the current thread when the current time has passed t. Any previous call conditions for the current thread are wiped out.
#include "npthread.h" int64 p; int status; npthread_child(p); status = npthread_childstatus();npthread_child tells the npthread library to call the current thread if child process p has just exited. Any previous call conditions for the current thread are wiped out.
Once that call happens, npthread_childstatus() returns the child's exit status, in the following form:
#include "npthread.h" int64 p; npthread_watchflag(p); npthread_waveflag(p); p = npthread_newflag();npthread_watchflag tells the npthread library to call the current thread if user-defined flag number p has just been waved. Any previous call conditions for the current thread are wiped out.
npthread_waveflag waves user-defined flag number p.
npthread_newflag returns a new flag number each time it is called: 1, 2, 3, etc. Libraries should use npthread_newflag to obtain their flag numbers so that independent libraries do not bump into each other.
Do not attempt to use user-defined flag waves as counters. If a thread is watching a flag, and the flag is waved twice in quick succession before the thread has a chance to wake up, the thread will be called once, not twice.
#include "npthread.h" int64 d; npthread_read(d); npthread_write(d);npthread_read tells the npthread library to call the current thread when file descriptor d is readable. npthread_write tells the npthread library to call the current thread when file descriptor d is writable.
The descriptor must already be open and known to the io library; you must use io_fd() to register descriptors not obtained from io_pipe() etc. The descriptor must remain open until the call condition is changed.
Any previous call conditions for the current thread are wiped out. For example, the npthread_sleepnutil in
npthread_sleepuntil(tai6464_add(tai6464_now(),networktimeout)); npthread_read(6); return 1;has no effect. If descriptor 6 is not readable, the thread will not be called again, even after networktimeout expires. In contrast,
io_timeout(6,tai6464_add(tai6464_now(),networktimeout)); npthread_read(6); return 1;will impose a time limit on the readability of descriptor 6.
Do not assume that data can actually be read or written merely because of a previous npthread_read or npthread_write. Data that is readable when npthread_start decides to call this thread might be read a moment later by another process. Buffer space available for writing when npthread_start decides to call this thread might be used a moment later by another process.
Do not assume that data has ever been readable or writable merely because of a previous npthread_read or npthread_write. The low-level UNIX routines used by io_wait could have failed to allocate memory; in this case, npthread_start has no choice but to call all threads waiting for descriptors.
#include "npthread.h" npthread_asap();npthread_asap tells the npthread library to call the current thread whenever possible. This is how the thread starts out after npthread_add.