当线程遇到信号
信号是Unix系统中报告各类事件的机制。在设计信号系统的时候,还没有线程这个东西。Unix默认总是有一个进程来响应某个信号。所以当线程被加入到Unix系统后,如何处理信号就成了标准迫需解决的问题。
APUE中也提到了多线程时代下的信号处理问题,但是讲得比较简略。有些问题我还是弄不明白,所以就写了下面几个实验来验证下。
注,以下实验中创建和取消线程的代码从略
1. 同个signal是否发送到每一个线程
这部分代码很简单,就不贴出了。就是创建两个线程,然后在绑定的线程处理函数中输出tid。 测试结果,发现Linux对线程的处理是这样的,无论你创建了多少个线程,从头到尾都只有一个线程可以接收各种信号,其他线程都无法响应该信号。 只让某一个线程统一处理各种信号,这也正好是多线程程序处理信号的最佳方法。一般情况下,用户得使用sigaddser...等等来实现在别的线程中屏蔽所有信号,只让唯一的线程接收信号。 注意,即使在Linux下,总是只有一个线程可以处理所有的信号,我们也应该看到,这个线程的选择是随机的。所以,如果你不想某些工作线程受到打扰的话,你还是需要采用上面的“最佳实践”,在其他线程中屏蔽掉信号,只让选定的线程处理之。
ok,那么进一步说,假如我在某些线程中重绑定了信号处理程序,让它屏蔽掉某些信号,这时会发生什么事呢?
void rebind_sig_handler(int signo)
{
printf("rebind %d in thread %lu\n", signo, pthread_self());
if (signal(signo, sig_usr) == SIG_ERR) {
perror("rebind signal failed");
exit(1);
}
}
// sig_usr中屏蔽掉某些信号。因为接下来会讲到,所以这里不说了。
实验证明,如果我们在默认的处理信号的线程中屏蔽了A信号,那么A信号就会交由剩下的未屏蔽A信号的线程处理。而且有趣的是,所有的信号都会转交给该未屏蔽线程来处理。假如该线程又屏蔽了B信号,而且总共只有两个线程,那么B信号会交由屏蔽了A信号的线程处理。
2. 某个线程重新绑定handler是否影响其他线程
前面演示的重新绑定的情况就已经显示了,当一个线程重新绑定handler时,其他线程也会一并受影响。
3. cond_wait状态下是否有signal丢失的问题
测试代码如下:
pthread_mutex_lock(&mutex_second);
printf("thread %lu gets the lock\n", pthread_self());
if (pthread_sigmask(SIG_BLOCK, &blockSet, &prevMask) == -1) {
perror("block signal failed");
exit(1);
}
pthread_cond_wait(&cond, &mutex_second);
pause();
pthread_mutex_unlock(&mutex_second);
结果: 所有的线程依然能正常接收信号
可以看出,cond_wait下signal不会丢失。具体情况如同调用了sleep一样。处于cond_wait下的线程会被短时间内唤醒,在处理完signal之后又继续cond_wait下去。如果在signal handler中调用了cond_signal或cond_broadcast怎么样?在这里我没有测。
4. 因为pthread_mutex_lock而被阻塞的线程是否有signal丢失的问题
测试代码如下:
结果同第三项cond_wait部分。可见,pthread_mutex_lock阻塞下的线程不会有signal丢失的问题。
5. 如何在多线程下屏蔽某些信号
如之前的代码,使用
#include <signal.h>
pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
//sigset_t blockSet;
//sigset_t prevMask;
//sigemptyset(&blockSet);
//sigaddset(&blockSet, SIGUSR1);
//if (pthread_sigmask(SIG_BLOCK, &blockSet, &prevMask) == -1) {
// perror("block signal failed");
// exit(1);
//}
大体上的用法同sigprocmask()。据说在Linux上,这两个函数共享同样的实现。
// 解除屏蔽的代码则如下:
if (pthread_sigmask(SIG_SETMASK, &prevMask, NULL) == -1) {
perror("reset signal mask failed");
exit(1);
}
在多线程程序下的信号处理程序需要注意什么
- 遵循前面提到的只拿出单个线程来处理信号(为了实现同步接收异步产生的信号)
- 注意在信号处理函数中不要使用非线程安全的函数
- 除非做到了第一点,不要在信号处理函数中调用非异步信号安全(non-async-signal-safe)的函数。(包括了全部的Pthread函数) 这里有份异步信号安全函数白名单:http://docs.oracle.com/cd/E19455-01/806-5257/gen-26/index.html