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.
Remember that, aside from signal-catching functions, signal actions affect the process. Sending the SIGKILL signal to a specific thread using pthread_kill
will kill the process, not just the specified thread. Use pthread_cancel
to get rid of a particular thread (see Section 5.3). Sending SIGSTOP to a thread will stop all threads in the process until a SIGCONT is sent by some other process.
The raise
function specified by ANSI C has traditionally been mapped to a kill for the current process. That is, raise(SIGABRT)
is usually the same as kill(getpid (), SIGABRT)
.
With multiple threads, code calling raise is most likely to intend that the signal be sent to the calling thread, rather than to some arbitrary thread within the process. Pthreads specifies that raise (SIGABRT) is the same as pthread_kill(pthread_self(),SIGABRT)
.
The following program, susp.c, uses pthread_kill
to implement a portable 'suspend and resume' (or, equivalently, 'suspend and continue') capability much like that provided by the Solaris 'UI threads' interfaces thr_suspend
and thr_continue
[6]. You call the thd_suspend
function with the pthread_t
of a thread, and when the function returns, the specified thread has been thd_continue
is made with the same pthread_t
.
A request to suspend a thread that is already suspended has no effect. Calling thd_continue
a single time for a suspended thread will cause it to resume execution, even if it had been suspended by multiple calls to thd_suspend
. Calling thd_continue
for a thread that is not currently suspended has no effect.
Suspend and resume are commonly used to solve some problems, for example, multithread garbage collectors, and may even work sometimes if the programmer is very careful. This emulation of suspend and resume may therefore be valuable to the few programmers who really need these functions. Beware, however, that should you suspend a thread while it holds some resource (such as a mutex), application deadlock can easily result.
6 The symbol ITERATIONS defines how many times the 'target' threads will loop. If this value is set too small, some or all of the threads will terminate before the main thread has been able to suspend and continue them as it desires. If that happens, the program will fail with an error message — increase the value of ITERATIONS until the problem goes away.
12 The variable sentinel is used to synchronize between a signal-catching function and another thread. 'Oh?' you may ask, incredulously. This mechanism is not perfect — the suspending thread (the one calling thd_suspend) waits in a loop, yielding the processor until this sentinel changes state. The volatile storage attribute ensures that the signal-catching function will write the value to memory. Remember, you cannot use a mutex within a signal-catching function.
22-40 The suspend_signal_handler function will be established as the signal-catching function for the 'suspend' signal, SIGUSR1. It initializes a signal mask to block all signals except SIGUSR2, which is the 'resume' signal, and then waits for that signal by calling sigsuspend. Just before suspending itself, it sets the sentinel variable to inform the suspending thread that it is no longer executing user code — for most practical purposes, it is already suspended.
The purpose for this synchronization between the signal-catching function and thd_suspend is that, to be most useful, the thread calling thd_suspend must be able to know when the target thread has been successfully suspended. Simply calling pthread_kill is not enough, because the system might not deliver the signal for a substantial period of time; we need to know when the signal has been received.
47-51 The resume_signal_handler function will be established as the signal-catching function for the 'resume'