转自 http://blog.chinaunix.net/uid-10231348-id-3036633.html
部分参考 https://blog.csdn.net/intrepyd/article/details/4473533
类UNIX信号以前是专门未进程设计的,比线程早出现很多年。如果要在线程模型中保持原来在进程中信号语义不变,困难。因此应该 避免信号和线程一起使用 。但是安全将二者分开也是不可能或不实际的。只要有可能的话,应该在主线程内使用pthread_sigmask() 来屏蔽信号,然后同步地在专用的线程中使用sigwait() 来处理信号。
为了理解信号模型是怎样映射到线程模型的,我们需要知道信号模型的哪些方面是影响进程层面的(process-wide),哪些方面只会影响某个线程的。下面列出几点:
-
signal actions 是process-wide。如果一个没有处理的信号的默认动作是停止SIGSTOP或终止SIGKILL(该动作是让整个进程停止或终止,而不是只针对某个线程),那么不管这个信号是发送给哪个线程,整个进程都会停止或终止。
-
.signal dispositions信号部署是process-wide。一个进程中的所有线程对某个信号都共享相同的信号处理函数。如果线程A使用sigaction()对某个信号,比如SIGINT,建立了一个信号处理函数。那么当SIGINT发送到线程B时,信号处理函数也会被调用。
-
下面几种情况,把信号发送到某个指定的线程。
A…某个特定硬件指令执行后(在该线程内执行的),产生的信号,将会发送到该线程内。比如SIGBUS,SIGFPE,SIGILL,SIGSEGV。
B.当线程尝试向一个broken pipe写数据时,会产生一个SIGPIPE.
C.使用pthread_kill()或者pthread_sigqueue()。这些函数允许一个线程发送信号到另一个线程(同一进程中)。
其他情况都是把信号发送到整个进程(比如,kill()和sigqueue())。
-
当一个信号被发送到一个多线程的进程中(注意是发送到进程)。内核会选择该进程中的任意线程来处理该信号。这种做法是为了保持进程中信号的语意,保证不会在多线程进程中一个信号多次被执行。
-
信号掩码(signal mask)是线程私用的。在多线程的进程中,不存在process-wide的信号掩码。线程可以使用pthread_sigmask()来独立的屏蔽某些信号。通过这种方法,程序员可以控制哪些线程响应哪些信号。当线程被创建时,它将继承创建它的线程的信号掩码。
-
内核为每个线程和进程分别维护了一个未决信号的表。当使用sigpending()时,该函数返回的是整个进程未决信号表和调用该函数的线程的未决信号表的并集。当新线程被创建时,线程的pending signals被设置为空。当线程A阻塞某个信号S后,发送到A中的信号S将会被挂起,直到线程取消了对信号S的阻塞。
-
如果一个信号处理函数打断了pthread_mutex_lock(),该函数会自动的重新执行。如果信号处理函数打断了pthread_cond_wait()(参见POSIX线程-条件变量),该函数要么自动重新自行(linux是这样实现的),或者返回0(这时应用要检查返回值,判断是否为假唤醒)。
-
可选的信号栈是线程私有的(将会在线程总结时讨论)。新创建的线程不会继承创建它的线程的信号栈。
一个函数要么是可重入的,要么是不能被信号处理函数打断的。调用非异步信号安全的函数是危险的。比如,考虑在线程A中,我们调用malloc()来进行内存分配,malloc()刚用互斥量锁住了全局链表,这是异步信号到达,在信号处理函数中也调用malloc(),这时该函数会阻塞在互斥量上,形成死锁(这个例子在单线程的进程中也会出现)。Pthread API不是async-signal-safe(异步信号安全)的,也就是说在信号处理函数中不要使用pthread相关的函数。
解决办法是,在不打断程序的前提下,将所有的异步信号都在同一处处理。在单线程程序中是做不到的,因为所有发送的信号都会打断程序。在多线程程序中,可以单独创建一个线程来接受信号,处理需要的信号,不打断其他线程的工作。
但是,信号处理函数也可能被其他信号打断,应该在处理信号前,对所有的异步信号进行阻塞,等工作处理完毕后,再回复阻塞的信号。
int sigwait(const sigset_t *set, int *sig)
注意点:
1.调用sigwait()等待的信号必须在调用线程中屏蔽,通常我们在所有线程中都会屏蔽。
2.信号仅仅被交付一次。如果两个线程在sigwait()上阻塞,只有一个线程(不确定的线程)将收到送给进程的信号。这意味着不能让两个独立的子系统使用sigwait()来捕获相同的信号。
使用方法:
在主线程中:
sigset_t signal_set;
sigemptyset(&signal_set)
sigfillset(&signal_set);
pthread_sigmask(SIG_BLOCK, &signal_set, NULL)
pthread_create();
专门处理信号的线程(响应SIGINT)
sigset_t signal_set;
int sig_num;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGINT);
while(1){
sigwait(&signal_set, &sig_num);
if (sig_num == SIGINT)
/*do some thing*/
/*这里可以调用非asyn-signal-safe的函数*/
}
注意信号处理函数是安全的!
线程和exec()
当在线程中调用exec(),该线程被完全的替代,线程ID不确定。除了被替代的线程,其他线程都被销毁。所有进程的互斥量和条件变量消失 。
线程和fork()
挡在多线程中调用fork(),只有调用fork的线程被复制到子进程。
- 虽然只有调用fork额线程被复制到子进程,但是子进程的全局变量,互斥量,条件变量的状态都和父进程中的一样。
- thread-specific data的销毁函数和清楚函数都不会被调用。在多线程中调用fork可能会引起内存泄露。
由于存在以上问题,因此在线程中调用fork后,应该在子进程中调用exec(),因为exec()能让父进程中的所有互斥量,条件变量在子进程中统统消失。
线程的信号屏蔽字
每个线程都有自己的信号屏蔽字,但是信号处理函数是进程中所有线程共享的,折意味着尽管单个线程可以阻止某些信号,当线程修改了某个信号的相关处理行为后所有的线程必须共享这个处理行为的改变。
进程中的信号是递送到单个线程的。如果信号与硬件故障或者定时器有关,则信号被递送到引起该事件的线程中。而其他的信号则被随机发送到任意一个线程。
但要注意,闹钟定时器是进程资源,并且所有的线程共享相同的alarm,所以进程中的多个线程不可能互不干扰地使用闹钟定时器。
线程必须使用pthread_sigmask函数,替代进程使用的sigprocmask,为自己设置信号屏蔽字。