The Solaris 2.5 Threads Library


Solaris 2.5 provides an implementation of the many-to-many model [Powell et al. 1991] and introduces a new set of vocabulary: a kernel thread in Solaris is referred to as a lightweight process (LWP), while a user thread is simply a thread. The Solaris threads package is intended to isolate the programmer as much as possible from the notion of LWPs.

You may wish to get some background information on other threads package implementation models before proceeding.

User-level Thread Scheduling

User-level Thread States

Unbound threads in Solaris may be in one five states: Stopped, Blocked, Run queue, Dispatchable or On LWP. A thread that has been suspended is Stopped, while a thread blocked on a synchronization primitive is Blocked. If a thread is runnable but is not running on an LWP, then it is either on the Run queue or, if an LWP has been found to run the thread, it is Dispatchable. Once a runnable thread is picked up by an LWP, it is On LWP. While a thread cannot be actually running on a CPU unless it is On LWP, being On LWP does not imply that a thread is running on a CPU; the underlying LWP itself could be sleeping, waiting for a processor, etc.

Thread-LWP Interaction

Solaris implements the multiplexing of user-level threads onto LWPs by maintaining a pool of LWPs. Any unbound thread may run on any LWP in the pool; when a thread is ready to run (i.e. in the user-level run queue), the user-level scheduler takes an LWP out of the pool and assigns it to run the newly runnable thread (changing the thread's state to On LWP). This LWP will continue to run the thread until either a thread at a higher priority becomes runnable or the thread blocks on a synchronization primitive. Thus, the user-level threads library is nonpreemptable when all threads have the same priority.

When an LWP is idle (i.e. the LWP is in the pool and no threads are runnable), the user-level scheduler parks it in the kernel. If a thread becomes runnable while LWPs are parked, the user-level scheduler unparks one of the LWPs. Once an LWP is unparked, it dequeues and runs a user thread from the user-level run queue.

LWP Pool Management

The size of the LWP pool has a critical impact on the performance of the many-to-many model: if the number of LWPs in the pool is nearly equal to the number of threads, the implementation will act much like the one-to-one model. Conversely, if there are very few LWPs in the pool, the implementation will act like the many-to-one model.

Of particular concern is the risk of deadlock with an excessively small pool: one thread may block on a resource in the kernel and go to sleep, and by so doing block the LWP needed to run the resource-holder. To solve this problem, the threads package makes a minimal guarantee to the threads programmer: progress will always be made. This is implemented through the use of the SIGWAITING signal. When the kernel realizes that all of a process's LWPs are blocked at the kernel level, it drops a SIGWAITING on the process. Upon receipt of the signal, the user-level threads package decides whether or not to create a new LWP, on the basis of the number of runnable threads. The SIGWAITING mechanism makes no guarantees about optimal use of LWPs on a multiprocessor. Specifically, a process may have many more runnable user-level threads than it has LWPs, but it does not receive a SIGWAITING until all LWPs are blocked. Thus, even if there are processors available and work to be done, the SIGWAITING mechanism does not guarantee that there is a sufficient number of LWPs to run the user threads on the available processors. If the programmer wishes to use unbound threads and take advantage of all available processors, he or she is required to advise the library on the number of LWPs required.

[ Top | Introduction | Solaris | Threadmon | References ]

The text of this web document was taken from a paper by Bryan M. Cantrill and Thomas W. Doeppner Jr., Department of Computer Science, Brown University, Providence, RI 02912-1910
Greg Foxman (