JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Multithreaded Programming Guide     Oracle Solaris 11 Express 11/10
search filter icon
search icon

Document Information

Preface

1.  Covering Multithreading Basics

2.  Basic Threads Programming

3.  Thread Attributes

4.  Programming with Synchronization Objects

5.  Programming With the Solaris Software

Forking Issues in Process Creation

Fork-One Model

Fork-One Safety Problem and Solution

Virtual Forks-vfork

Solution: pthread_atfork

Fork-All Model

Choosing the Right Fork

Process Creation: exec and exit Issues

Timers, Alarms, and Profiling

Timers

Alarms

Profiling a Multithreaded Program

Nonlocal Goto: setjmp and longjmp

Resource Limits

LWPs and Scheduling Classes

Timeshare Scheduling

Realtime Scheduling

Fair Share Scheduling

Fixed Priority Scheduling

Extending Traditional Signals

Synchronous Signals

Asynchronous Signals

Continuation Semantics

Operations on Signals

Setting the Thread's Signal Mask

Sending a Signal to a Specific Thread

Waiting for a Specified Signal

Waiting for Specified Signal Within a Given Time

Thread-Directed Signals

Completion Semantics

Signal Handlers and Async-Signal Safety

Interrupted Waits on Condition Variables

I/O Issues

I/O as a Remote Procedure Call

Tamed Asynchrony

Asynchronous I/O

Asynchronous I/O Operations

Waiting for I/O Operation to Complete

Shared I/O and New I/O System Calls

Alternatives to getc and putc

6.  Programming With Solaris Threads

7.  Safe and Unsafe Interfaces

8.  Compiling and Debugging

9.  Programming Guidelines

A.  Extended Example: A Thread Pool Implementation

Index

Extending Traditional Signals

The traditional UNIX signal model is extended to threads in a fairly natural way. The key characteristics are that the signal disposition is process-wide, but the signal mask is per-thread. The process-wide disposition of signals is established using the traditional mechanisms signal(3C), sigaction(2), and so on.

When a signal handler is marked SIG_DFL or SIG_IGN, the action on receipt of a signal is performed on the entire receiving process. These signals include exit, core dump, stop, continue, and ignore. The action on receipt of these signals is carried out on all threads in the process. Therefore, the issue of which thread picks the signal is nonexistent. The exit, core dump, stop, continue, and ignore signals have no handlers. See the signal.h(3HEAD) man page for basic information about signals.

Each thread has its own signal mask. The signal mask lets a thread block some signals while the thread uses memory or another state that is also used by a signal handler. All threads in a process share the set of signal handlers that are set up by sigaction(2) and its variants.

A thread in one process cannot send a signal to a specific thread in another process. A signal sent by kill(2), sigsend(2), or sigqueue(3RT) to a process is handled by any receptive threads in the process.

Signals are divided into the following categories: traps, exceptions, and interrupts. Traps and exceptions are synchronously generated signals. Interrupts are asynchronously generated signals.

As in traditional UNIX, if a signal is pending, additional occurrences of that signal normally have no additional effect. A pending signal is represented by a bit, not by a counter. However, signals that are posted through the sigqueue(3RT) interface allow multiple instances of the same signal to be queued to the process.

As is the case with single-threaded processes, when a thread receives a signal while blocked in a system call, the thread might return early. When a thread returns early, the thread either returns an EINTR error code, or, in the case of I/O calls, with fewer bytes transferred than requested.

Of particular importance to multithreaded programs is the effect of signals on pthread_cond_wait(3C). This call usually returns without error, a return value of zero, only in response to a pthread_cond_signal(3C) or a pthread_cond_broadcast(3C). However, if the waiting thread receives a traditional UNIX signal, pthread_cond_wait() returns with a return value of zero even though the wakeup was spurious.

Synchronous Signals

Traps, such as SIGILL, SIGFPE, and SIGSEGV, result from an operation on the thread, such as dividing by zero or making reference to nonexistent memory. A trap is handled only by the thread that caused the trap. Several threads in a process can generate and handle the same type of trap simultaneously.

The idea of signals to individual threads is easily extended for synchronously generated signals. The handler is invoked on the thread that generated the synchronous signal.

However, if the process chooses not to establish an appropriate signal handler, the default action is taken when a trap occurs. The default action occurs even if the offending thread is blocked on the generated signal. The default action for such signals is to terminate the process, perhaps with a core dump.

Such a synchronous signal usually means that something is seriously wrong with the whole process, not just with a thread. In this case, terminating the process is often a good choice.

Asynchronous Signals

Interrupts, such as SIGINT and SIGIO, are asynchronous with any thread and result from some action outside the process. These interrupts might be signals sent explicitly by another process, or might represent external actions such as a user typing a Control-C.

An interrupt can be handled by any thread whose signal mask allows the interrupt. When more than one thread is able to receive the interrupt, only one thread is chosen.

When multiple occurrences of the same signal are sent to a process, then each occurrence can be handled by a separate thread. However, the available threads must not have the signal masked. When all threads have the signal masked, then the signal is marked pending and the first thread to unmask the signal handles the signal.

Continuation Semantics

Continuation semantics are the traditional way to deal with signals. When a signal handler returns, control resumes where the process was at the time of the interruption. This control resumption is well suited for asynchronous signals in single-threaded processes, as shown in Example 5-1.

This control resumption is also used as the exception-handling mechanism in other programming languages, such as PL/1.

Example 5-1 Continuation Semantics

unsigned int nestcount;

unsigned int A(int i, int j) {
    nestcount++;

    if (i==0)
        return(j+1)
    else if (j==0)
        return(A(i-1, 1));
    else
        return(A(i-1, A(i, j-1)));
}

void sig(int i) {
    printf("nestcount = %d\n", nestcount);
}

main() {
    sigset(SIGINT, sig);
    A(4,4);
}

Operations on Signals

This section describes the operations on signals.

Setting the Thread's Signal Mask

pthread_sigmask(3C) does for a thread what sigprocmask(2) does for a process. pthread_sigmask() sets the thread's signal mask. When a new thread is created, its initial mask is inherited from its creator.

The call to sigprocmask() in a multithreaded process is equivalent to a call to pthread_sigmask(). See the sigprocmask(2) man page for more information.

Sending a Signal to a Specific Thread

pthread_kill(3C) is the thread analog of kill(2). A pthread_kill() call sends a signal to a specific thread. A signal that is sent to a specified thread is different from a signal that is sent to a process. When a signal is sent to a process, the signal can be handled by any thread in the process. A signal sent by pthread_kill() can be handled only by the specified thread.

You can use pthread_kill() to send signals only to threads in the current process. Because the thread identifier, type thread_t, is local in scope, you cannot name a thread outside the scope of the current process.

On receipt of a signal by the target thread, the action invoked (handler, SIG_DFL, or SIG_IGN) is global, as usual. If you send SIGXXX to a thread, and SIGXXX to kill a process, the whole process is killed when the target thread receives the signal.

Waiting for a Specified Signal

For multithreaded programs, sigwait(2) is the preferred interface to use because sigwait() deals well with asynchronously generated signals.

sigwait() causes the calling thread to wait until any signal identified by the sigwait() function's set argument is delivered to the thread. While the thread is waiting, signals identified by the set argument are unmasked, but the original mask is restored when the call returns.

All signals identified by the set argument must be blocked on all threads, including the calling thread. Otherwise, sigwait() might not work correctly.

Use sigwait() to separate threads from asynchronous signals. You can create one thread that listens for asynchronous signals while you create other threads to block any asynchronous signals set to this process.

The following example shows the syntax of sigwait() .

#include <signal.h>
int sigwait(const sigset_t *set, int *sig
);

When the signal is delivered, sigwait() clears the pending signal and places the signal number in sig. Many threads can call sigwait() at the same time, but only one thread returns for each signal that is received.

With sigwait(), you can treat asynchronous signals synchronously. A thread that deals with such signals calls sigwait() and returns as soon as a signal arrives. By ensuring that all threads, including the caller of sigwait(), mask asynchronous signals, ensures signals are handled only by the intended handler and are handled safely.

By always masking all signals in all threads and calling sigwait() as necessary, your application is much safer for threads that depend on signals.

Usually, you create one or more threads that call sigwait() to wait for signals. Because sigwait() retrieves even masked signals, be sure to block the signals of interest in all other threads so the signals are not accidentally delivered.

When a signal arrives, a signal-handling thread returns from sigwait() , handles the signal, and calls sigwait() again to wait for more signals. The signal-handling thread is not restricted to using Async-Signal-Safe functions. The signal-handling thread can synchronize with other threads in the usual way. The Async-Signal-Safe category is defined in MT Interface Safety Levels.


Note - sigwait() cannot receive synchronously generated signals.


Waiting for Specified Signal Within a Given Time

sigtimedwait(3RT) is similar to sigwait(2) except that sigtimedwait() fails and returns an error when a signal is not received in the indicated amount of time. See the sigtimedwait(3RT) man page for more information.

Thread-Directed Signals

The UNIX signal mechanism is extended with the idea of thread-directed signals. Thread-directed signals are just like ordinary asynchronous signals, except that thread-directed signals are sent to a particular thread instead of to a process.

A separate thread that waits for asynchronous signals can be safer and easier than installing a signal handler that processes the signals.

A better way to deal with asynchronous signals is to treat these signals synchronously. By calling sigwait(2), a thread can wait until a signal occurs. See Waiting for a Specified Signal.

Example 5-2 Asynchronous Signals and sigwait(2)

main() {
    sigset_t set;
    void runA(void);
    int sig;

    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    pthread_create(NULL, 0, runA, NULL, PTHREAD_DETACHED, NULL);

    while (1) {
        sigwait(&set, &sig);
        printf("nestcount = %d\n", nestcount);
        printf("received signal %d\n", sig);
    }
}

void runA() {
    A(4,4);
    exit(0);
}

This example modifies the code of Example 5-1. The main routine masks the SIGINT signal, creates a child thread that calls function A of the previous example, and issues sigwait() to handle the SIGINT signal.

Note that the signal is masked in the compute thread because the compute thread inherits its signal mask from the main thread. The main thread is protected from SIGINT while, and only while, the thread is not blocked inside of sigwait().

Also, note that no danger exists of having system calls interrupted when you use sigwait().

Completion Semantics

Another way to deal with signals is with completion semantics.

Use completion semantics when a signal indicates that something so catastrophic has happened that no reason exists to continue executing the current code block. The signal handler runs instead of the remainder of the block that had the problem. In other words, the signal handler completes the block.

In Example 5-3, the block in question is the body of the then part of the if statement. The call to setjmp(3C) saves the current register state of the program in jbuf and returns 0, thereby executing the block.

Example 5-3 Completion Semantics

sigjmp_buf jbuf;
void mult_divide(void) {
    int a, b, c, d;
    void problem();

    sigset(SIGFPE, problem);
    while (1) {
        if (sigsetjmp(&jbuf) == 0) {
            printf("Three numbers, please:\n");
            scanf("%d %d %d", &a, &b, &c);
            d = a*b/c;
            printf("%d*%d/%d = %d\n", a, b, c, d);
        }
    }
}

void problem(int sig) {
    printf("Couldn't deal with them, try again\n");
    siglongjmp(&jbuf, 1);
}

If a SIGFPE floating-point exception occurs, the signal handler is invoked.

The signal handler calls siglongjmp(3C), which restores the register state saved in jbuf, causing the program to return from sigsetjmp() again. The registers that are saved include the program counter and the stack pointer.

This time, however, sigsetjmp(3C) returns the second argument of siglongjmp(), which is 1. Notice that the block is skipped over, only to be executed during the next iteration of the while loop.

You can use sigsetjmp(3C) and siglongjmp(3C) in multithreaded programs. Be careful that a thread never does a siglongjmp() that uses the results of another thread's sigsetjmp().

Also, sigsetjmp() and siglongjmp() restore as well as save the signal mask, but setjmp(3C) and longjmp(3C) do not.

Use sigsetjmp() and siglongjmp() when you work with signal handlers.

Completion semantics are often used to deal with exceptions. In particular, the Sun Ada programming language uses this model.


Note - Remember, sigwait(2) should never be used with synchronous signals.


Signal Handlers and Async-Signal Safety

A concept that is similar to thread safety is Async-Signal safety. Async-Signal-Safe operations are guaranteed not to interfere with operations that are being interrupted.

The problem of Async-Signal safety arises when the actions of a signal handler can interfere with the operation that is being interrupted.

For example, suppose a program is in the middle of a call to printf(3C), and a signal occurs whose handler calls printf(). In this case, the output of the two printf() statements would be intertwined. To avoid the intertwined output, the handler should not directly call printf() when printf() might be interrupted by a signal.

This problem cannot be solved by using synchronization primitives. Any attempt to synchronize between the signal handler and the operation being synchronized would produce an immediate deadlock.

Suppose that printf() is to protect itself by using a mutex. Now, suppose that a thread that is in a call to printf() and so holds the lock on the mutex is interrupted by a signal.

If the handler calls printf(), the thread that holds the lock on the mutex attempts to take the mutex again. Attempting to take the mutex results in an instant deadlock.

To avoid interference between the handler and the operation, ensure that the situation never arises. Perhaps you can mask off signals at critical moments, or invoke only Async-Signal-Safe operations from inside signal handlers.

The only routines that POSIX guarantees to be Async-Signal-Safe are listed in Table 5-2. Any signal handler can safely call in to one of these functions.

Table 5-2 Async-Signal-Safe Functions

_Exit()
fpathconf()
read()
sigset()
_exit()
fstat()
readlink()
sigsuspend()
abort()
fsync()
recv()
sockatmark()
accept()
ftruncate()
recvfrom()
socket()
access()
getegid()
recvmsg()
socketpair()
aio_error()
geteuid()
rename()
stat()
aio_return()
getgid()
rmdir()
symlink()
aio_suspend()
getgroups()
select()
sysconf()
alarm()
getpeername()
sem_post()
tcdrain()
bind()
getpgrp()
send()
tcflow()
cfgetispeed()
getpid()
sendmsg()
tcflush()
cfgetospeed()
getppid()
sendto()
tcgetattr()
cfsetispeed()
getsockname()
setgid()
tcgetattr()
cfsetospeed()
getsockopt()
setpgid()
tcsendbreak()
chdir()
getuid()
setsid()
tcsetattr()
chmod()
kill()
setsockopt()
tcsetpgrp()
chown()
link()
setuid()
time()
clock_gettime()
listen()
shutdown()
timer_getoverrun()
close()
lseek()
sigaction()
timer_gettime()
connect()
lstat()
sigaddset()
timer_settime()
creat()
mkdir()
sigdelset()
times()
dup()
mkfifo()
sigemptyset()
umask()
dup2()
open()
sigfillset()
uname()
execle()
pathconf()
sigismember()
ulink()
execve()
pause()
sleep()
utime()
fchmod()
pipe()
signal()
wait()
fchown()
poll()
sigpause()
waitpid()
fcntl()
posix_trace_event()
sigpending()
write()
fdatasync()
pselect()
sigprocmask()
fork()
raise()
sigqueue()

Interrupted Waits on Condition Variables

When an unmasked caught signal is delivered to a thread waiting on a condition variable, when the signal handler returns, the thread returns from the condition wait function with a spurious wakeup: pthread_cond_wait() and pthread_cond_timedwait() return 0 even though no call to pthread_cond_signal() or pthread_cond_broadcast() was made by another thread. Whether SA_RESTART has been specified as a flag to sigaction() has no effect here. The pthread_cond_wait() and pthread_cond_timedwait() functions are not automatically restarted. In all cases, the associated mutex lock is reacquired before returning from the condition wait.

Re-acquisition of the associated mutex lock does not imply that the mutex is locked while the thread is executing the signal handler. The state of the mutex in the signal handler is undefined.