Signal management

Signals in multithreaded processes are an extension of signals in traditional single-threaded programs.

Signal management in multithreaded processes is shared by the process and thread levels, and consists of the following:
  • Per-process signal handlers
  • Per-thread signal masks
  • Single delivery of each signal

Signal handlers and signal masks

Signal handlers are maintained at process level. It is strongly recommended to use the sigwait subroutine when waiting for signals. The sigaction subroutine is not recommended because the list of signal handlers is maintained at process level and any thread within the process might change it. If two threads set a signal handler on the same signal, the last thread that called the sigaction subroutine overrides the setting of the previous thread call; and in most cases, the order in which threads are scheduled cannot be predicted.

Signal masks are maintained at thread level. Each thread can have its own set of signals that will be blocked from delivery. The sigthreadmask subroutine must be used to get and set the calling thread's signal mask. The sigprocmask subroutine must not be used in multithreaded programs; because unexpected behavior might result.

The pthread_sigmask subroutine is very similar to the sigprocmask subroutine. The parameters and usage of both subroutines are identical. When porting existing code to support the threads library, you can replace the sigprocmask subroutine with the pthread_sigmask subroutine.

Signal generation

Signals generated by some action attributable to a particular thread, such as a hardware fault, are sent to the thread that caused the signal to be generated. Signals generated in association with a process ID, a process group ID, or an asynchronous event (such as terminal activity) are sent to the process.

  • The pthread_kill subroutine sends a signal to a thread. Because thread IDs identify threads within a process, this subroutine can only send signals to threads within the same process.
  • The kill subroutine (and thus the kill command) sends a signal to a process. A thread can send a Signal signal to its process by executing the following call:
    kill(getpid(), Signal);
  • The raise subroutine cannot be used to send a signal to the calling thread's process. The raise subroutine sends a signal to the calling thread, as in the following call:
    pthread_kill(pthread_self(), Signal);
    This ensures that the signal is sent to the caller of the raise subroutine. Thus, library routines written for single-threaded programs can easily be ported to a multithreaded system, because the raise subroutine is usually intended to send the signal to the caller.
  • The alarm subroutine requests that a signal be sent later to the process, and alarm states are maintained at process level. Thus, the last thread that called the alarm subroutine overrides the settings of other threads in the process. In a multithreaded program, the SIGALRM signal is not necessarily delivered to the thread that called the alarm subroutine. The calling thread might even be terminated; and therefore, it cannot receive the signal.

Handling signals

Signal handlers are called within the thread to which the signal is delivered. The following limitations to signal handlers are introduced by the threads library:
  • Signal handlers might call the longjmp or siglongjmp subroutine only if the corresponding call to the setjmp or sigsetjmp subroutine is performed in the same thread.

    Usually, a program that wants to wait for a signal installs a signal handler that calls the longjmp subroutine to continue execution at the point where the corresponding setjmp subroutine is called. This cannot be done in a multithreaded program, because the signal might be delivered to a thread other than the one that called the setjmp subroutine, thus causing the handler to be executed by the wrong thread.

    Note: Using longjmp from a signal handler can result in undefined behavior.
  • No pthread routines can be called from a signal handler. Calling a pthread routine from a signal handler can lead to an application deadlock.

To allow a thread to wait for asynchronously generated signals, the threads library provides the sigwait subroutine. The sigwait subroutine blocks the calling thread until one of the awaited signals is sent to the process or to the thread. There must not be a signal handler installed on the awaited signal using the sigwait subroutine.

Typically, programs might create a dedicated thread to wait for asynchronously generated signals. Such a thread loops on a sigwait subroutine call and handles the signals. It is recommended that such a thread block all the signals. The following code fragment gives an example of such a signal-waiter thread:
#include <pthread.h>
#include <signal.h>

static pthread_mutex_t mutex;
sigset_t set;
static int sig_cond = 0;

void *run_me(void *id)
{
       int sig;
       int err;
       sigset_t sigs;
       sigset_t oldSigSet;
       sigfillset(&sigs);
       sigthreadmask(SIG_BLOCK, &sigs, &oldSigSet);

       err = sigwait(&set, &sig);

       if(err)
       {
              /* do error code */
       }
       else
       {
              printf("SIGINT caught\n");
              pthread_mutex_lock(&mutex);
              sig_cond = 1;
              pthread_mutex_unlock(&mutex);
       }

       return;
}

main()
{
       pthread_t tid;

       sigemptyset(&set);
       sigaddset(&set, SIGINT); 
       pthread_sigmask(SIG_BLOCK, &set, 0);

       pthread_mutex_init(&mutex, NULL);

       pthread_create(&tid, NULL, run_me, (void *)1);

       while(1)
       {
              sleep(1);
              /* or so something here */

              pthread_mutex_lock(&mutex);
              if(sig_cond)
              {
                     /* do exit stuff */
                     return;
              }
              pthread_mutex_unlock(&mutex);

       }

}

If more than one thread called the sigwait subroutine, exactly one call returns when a matching signal is sent. Which thread is awakened cannot be predicted. If a thread is going to do sigwait as well as handling of some other signals for which it is not doing sigwait, the user-defined signal handlers need to block the sigwaiter signals for the proper handling. Note that the sigwait subroutine provides a cancellation point.

Because a dedicated thread is not a real signal handler, it might signal a condition to any other thread. It is possible to implement a sigwait_multiple routine that would awaken all threads waiting for a specific signal. Each caller of the sigwait_multiple routine registers a set of signals. The caller then waits on a condition variable. A single thread calls the sigwait subroutine on the union of all registered signals. When the call to the sigwait subroutine returns, the appropriate state is set and condition variables are broadcasted. New callers to the sigwait_multiple subroutine cause the pending sigwait subroutine call to be canceled and reissued to update the set of signals being waited for.

Signal delivery

A signal is delivered to a thread, unless its action is set to ignore. The following rules govern signal delivery in a multithreaded process:

  • A signal whose action is set to terminate, stop, or continue the target thread or process respectively terminates, stops, or continues the entire process (and thus all of its threads). Single-threaded programs can thus be rewritten as multithreaded programs without changing their externally visible signal behavior.

    For example, consider a multithreaded user command, such as the grep command. A user can start the command in his favorite shell and then decide to stop it by sending a signal with the kill command. The signal should stop the entire process running the grep command.

  • Signals generated for a specific thread, using the pthread_kill or the raise subroutines, are delivered to that thread. If the thread has blocked the signal from delivery, the signal is set pending on the thread until the signal is unblocked from delivery. If the thread is terminated before the signal delivery, the signal will be ignored.
  • Signals generated for a process, using the kill subroutine for example, are delivered to exactly one thread in the process. If one or more threads called the sigwait subroutine, the signal is delivered to exactly one of these threads. Otherwise, the signal is delivered to exactly one thread that did not block the signal from delivery. If no thread matches these conditions, the signal is set pending on the process until a thread calls the sigwait subroutine specifying this signal or a thread unblocks the signal from delivery.

If the action associated with a pending signal (on a thread or on a process) is set to ignore, the signal is ignored.