blogWithMarkdown icon indicating copy to clipboard operation
blogWithMarkdown copied to clipboard

当线程遇到信号

Open spacewander opened this issue 11 years ago • 0 comments

信号是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_waitsignal不会丢失。具体情况如同调用了sleep一样。处于cond_wait下的线程会被短时间内唤醒,在处理完signal之后又继续cond_wait下去。如果在signal handler中调用了cond_signalcond_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);
}

在多线程程序下的信号处理程序需要注意什么

  1. 遵循前面提到的只拿出单个线程来处理信号(为了实现同步接收异步产生的信号)
  2. 注意在信号处理函数中不要使用非线程安全的函数
  3. 除非做到了第一点,不要在信号处理函数中调用非异步信号安全(non-async-signal-safe)的函数。(包括了全部的Pthread函数) 这里有份异步信号安全函数白名单:http://docs.oracle.com/cd/E19455-01/806-5257/gen-26/index.html

spacewander avatar Sep 23 '14 16:09 spacewander