Signals that are not "tied" to a specific hardware execution context are delivered to one arbitrary thread within the process. That means a SIGCHLD raised by a child process termination, for example, may not be delivered to the thread that created the child. Similarly, a call to kill results in a signal that may be delivered to any thread.
6.6.1 Signal actions
Thread 1 | Thread 2 | Comments |
sigaction(SIGFPE) | Thread 1's signal action active. | |
sigaction(SIGFPE) | Thread 2's signal action active. | |
Generate SIGFPE | Thread 1 signal is handled by the thread 2 signal action (but still in the context of thread 1). | |
Restore action | Thread 1 restores original signal action | |
restore action | Thread 2 restores thread 1 's signal action-original action is lost |
FIGURE 6.1
The synchronous "hardware context" signals, including SIGFPE, SIGSEGV, and SIGTRAP, are delivered to the thread that caused the hardware condition, never to another thread.
You cannot kill a thread by sending it a SIGKILL or stop a thread by sending it a SIGSTOR
Any signal that affected a process still affects the process when multiple threads are active, which means that sending a SIGKILL to a process or to any specific thread in the process (using pthread_kill, which we'll get to in Section 6.6.3) will terminate the process. Sending a SIGSTOP will cause all threads to stop until a SIGCONT is received. This ensures that existing process control functions continue to work — otherwise most threads in a process could continue running when you stopped a command by sending a SIGSTOP. This also applies to the default action of the other signals, for example, SIGSEGV, if not handled, will terminate the process and generate a core file — it will not terminate only the thread that generated the SIGSEGV.
What does this mean to a programmer? It has always been common wisdom that library code should not change signal actions — that this is exclusively the province of the main program. This philosophy becomes even more wise when you are programming with threads. Signal actions must always be under the control of a single component, at least, and to assign that responsibility to the main program makes the most sense in nearly all situations.
6.6.2 Signal masks
int pthread_sigmask (int how, const sigset_t *set, sigset_t *oset);
Each thread has its own private signal mask, which is modified by calling pthread_sigmask
. Pthreads does not specify what sigprocmask
does within a threaded process — it may do nothing. Portable threaded code does not call sigprocmask
. A thread can block or unblock signals without affecting the ability of other threads to handle the signal. This is particularly important for synchronous signals. It would be awkward if thread A were unable to process a SIGFPE because thread B was currently processing its own SIGFPE or. even worse, because thread C had blocked SIGFPE. When a thread is created, it inherits the signal mask of the thread that created it — if you want a signal to be masked everywhere, mask it first thing in main.
6.6.3 pthread_kill
int pthread_kill (pthread_t thread, int sig);
Within a process, one thread can send a signal to a specific thread (including itself) by calling pthread_kill
. When calling pthread_kill
, you specify not only the signal number to be delivered, but also the pthread_t
identifier for the thread to which you want the signal sent. You cannot use pthread_kill
to send a signal to a thread in another process, however, because a thread identifier (pthread_t
) is meaningful only within the process that created it.
The signal sent by pthread_kill
is handled like any other signal. If the "target" thread has the signal masked, it will be marked pending against that thread. If the thread is waiting for the signal in sigwait
(covered in Section 6.6.4), the thread will receive the signal. If the thread does not have the signal masked, and is not blocked in sigwait
, the current signal action will be taken.