信号管理
多线程进程中的信号是传统单线程程序中的信号扩展。
- 每个进程的信号处理程序
- 每个线程的信号掩码
- 每个信号的单个传递
信号处理程序和信号掩码
信号处理程序在进程级别维护。 强烈建议在等待信号时使用 sigwait 子例程。 不建议使用 sigaction 子例程,因为信号处理程序的列表是在进程级别维护的,该进程内的任何线程都可能更改它。 如果两个线程对同一信号设置信号处理程序,那么最后那个调用 sigaction 子例程的线程将覆盖前一个线程调用的设置,而且在大多数情况下无法预测线程调度的顺序。
信号掩码在线程级别维护。 每个线程可拥有自己的一组将被阻塞而无法传递的信号。 必须使用 sigthreadmask 子例程来获取和设置调用线程的信号掩码。 不得在多线程程序中使用 sigprocmask 子例程; 因为可能会导致意外行为。
pthread_sigmask 子例程与 sigprocmask 子例程很相似。 这两个子例程的参数和用法都是相同的。 在移植现有的代码以支持线程库时,可以将 sigprocmask 子例程替换为 pthread_sigmask 子例程。
信号生成
由于特殊线程而产生的某些操作所生成的信号(例如硬件故障)将被发送到导致生成该信号的线程。 所生成的与进程标识、进程组标识或异步事件(例如终端活动)相关联的信号将被发送到该进程。
- pthread_kill 子例程向线程发送信号。 因为线程标识标识了进程内的线程,所以该子例程只可将信号发送到同一进程内的线程。
- kill 子例程 (因此 kill 命令) 向进程发送信号。 线程可通过执行以下调用将 Signal 信号发送至其进程:
kill(getpid(), Signal); - 引发 子例程不能用于向调用线程的进程发送信号。 raise 子例程将信号发送到调用线程,如在以下调用中:
这确保了该信号被发送至 raise 子例程的调用程序。 因此,为单线程程序所写的库例程可简单地被移植到多线程系统,因为 raise 子例程通常用来将信号发送到调用程序。pthread_kill(pthread_self(), Signal); - 闹铃 子例程请求稍后将信号发送到进程,并且警报状态保持在进程级别。 因此,最后调用 alarm 子例程的线程会覆盖进程中其他线程的设置。 在多线程程序中,SIGALRM 信号并不一定需要传递给调用 alarm 子例程的线程。 甚至该调用线程可能被终止,因此无法接收该信号。
处理信号
为了允许线程等待异步生成的信号,线程库提供了 sigwait 子例程。 sigwait 子例程会阻塞调用线程,直到正在等待的信号之一被发送到该进程或该线程。 并不一定要为等待的信号使用 sigwait 子例程安装信号处理程序。
#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);
}
}如果多个线程调用 sigwait 子例程,在匹配信号被发送时正好返回一个调用。 无法预测唤醒哪个线程。 如果线程准备执行 sigwait 并处理其他某些没有执行 sigwait 的信号,那么用户定义的信号处理程序需要阻塞 sigwaiter 信号以进行适当的处理。 请注意 sigwait 子例程提供了一个取消点。
因为专用线程不是真正的信号处理程序,它可能将条件用信号发送至其他任何线程。 可以实现 sigwait_multiple 例程,该例程将唤醒所有等待某特定信号的线程。 sigwait_multiple 例程的每个调用程序都注册一组信号。 然后调用程序将等待条件变量。 单个线程对所有已注册信号的联合调用 sigwait 子例程。 当对 sigwait 子例程的调用返回时,将设置适当的状态并广播条件变量。 sigwait_multiple 子例程的新调用程序导致暂挂的 sigwait 子例程调用被取消并被重新发出,以更新等待的那组信号。
信号传递
信号将被传递给线程,除非其操作设为忽略。 以下规则管理多线程进程中的信号传递:
- 其操作设为终止、停止、或继续目标线程或进程的信号将分别终止、停止或继续整个进程(也就是其所有线程)。 这样可将单线程程序重写为多线程程序,而无需更改其外部可见的信号行为。
例如,对于多线程的用户命令,如 grep 命令。 用户可在其首选的 shell 中启动该命令,然后决定以 kill 命令发送信号来终止它。 该信号应该停止运行 grep 命令的整个进程。
- 使用 pthread_kill 或 raise 子例程为特定线程所生成的信号被传递给该线程。 如果该线程阻塞了信号的传递,那么该信号对该线程设为暂挂,直到该信号被取消阻塞而可以传递。 如果该线程在信号传递前被终止,那么忽略该信号。
- 为进程所生成的信号(例如,使用 kill 子例程)被精确传递给该进程中的一个线程。 如果一个或多个线程调用了 sigwait 子例程,那么该信号被精确传递给这些线程中的一个线程。 此外,该信号被精确传递给一个未阻塞信号传递的线程。 如果没有与这些条件匹配的线程,该信号设为暂挂直到有线程调用 sigwait 子例程来指定该信号,或有线程取消信号传递的阻塞。
如果与暂挂信号(线程或进程的)相关联的操作设为忽略,那么忽略该信号。