12.8 线程和信号
Posted U201013687
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了12.8 线程和信号相关的知识,希望对你有一定的参考价值。
在基于进程模型的信号处理已经比较吗麻烦了,引入线程后事情就更加复杂了。
每个线程拥有其自身的信号掩码,但是信号处理函数是被进程内的所有线程共享的,作为一个推论,独立的线程能够阻塞信号,但是如果一个线程修改与给定信号的处理动作的时候,所有的线程都会共享这一修改。也就是说,如果一个线程选择忽略一个给定信号,其他的线程可能会通过恢复默认呢处理或者是安装信号处理函数的方式撤销线程所做的忽略选择。
信号会只会被发送到进程内的一个线程。如果信号与硬件错误相关,那么信号通常被发送到造成时间发生的线程,其他信号,将被发送到任意一个线程。
在10.12节中,我们介绍了进程可以使用函数sigprocmask函数来阻塞信号的发送,然而,sigprocmask函数在多线程进程中的行为是未定义的,线程必须使用函数pthread_sigmask予以代替。
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
Returns: 0 if OK, error number on failure.
函数pthread_sigmask与函数sigprocmask相似,但是pthread_sigmask函数在失败的时候返回一个错误码,而函数sigprocmask在失败的时候返回-1并且设置errno变量。
参数how可以取三个不同的数值:
* SIG_BLOCK添加信号集到线程的信号掩码中;
* SIG_SETMASK使用信号集替换线程的信号掩码;
* SIG_UNBLOCK从线程的信号掩码中移除信号集;
如果oset参数不是null,那么线程的前一个信号掩码将被存储到oset指针指向的内存中。线程可以通过设置set参数为null的情况下获取其当前的信号掩码,着这种情况下,how参数将被忽略。
线程可以调用函数sigwait来等待一个或者多个信号的出现;
#include <signal.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);
Returns: 0 if OK, error number on failure.
参数set指定了线程正在等待信号的集合,参数signop指针指向的内存将会存储被发送的信号数量。
如果在sigwait函数被调用的时候,如果set中的一个信号处于挂起状态,那么函数sigwait就不会阻塞,而是直接返回,在返回之前,sigwait函数将会将从进程挂起的信号集中移除等到的信号。如果实现支持信号排队,并且一个信号的多个实例处于挂起状态,函数sigwait仅仅移除其中一个实例,其他实例仍然处于排队状态。
为了避免错误的行为,线程在调用函数sigwait之前必须先阻塞这些等待的信号。函数sigwait将会自动解除对应信号的阻塞,并且一直等待知道其中一个信号被发送,在返回之前,sigwait将会恢复线程的信号掩码,如果函数sigwait被调用的时候,对应的信号没有被阻塞,那么在完成函数sigwait之前会出现一个多于的时间窗口,在这个时间窗口内,信号可能被发送到了线程。
使用函数sigwait的一个好处就是可以简化信号处理函数的设计,因为它允许我们将异步信号处理当作是同步信号来看待,我们可以通过将指定信号添加到线程的信号掩码中,从而防止信号中断线程的执行,然后专门指定一个线程来处理信号,这些指定的线程在调用函数的时候不需要再考虑那些函数是信号处理安全的,因为在信号处理函数被调用的时候,线程处于正常的线程环境,而不是像传统情况那样:信号处理函数能够中断线程的正常执行。
如果多个线程由于调用了函数sigwait而阻塞在了同一个信号上,那么当信号被发送的时候,仅仅只有一个线程将会返回,如果一个信号被捕获,并且存在一个线程调用函数sigwait等待这一信号,如何发送信号是交给实现了决定的,实现可以允许sigwait返回或者是调用信号处理函数,但是不能同时执行这两个动作。
为了发送一个信号给进程,我们可以调用函数kill,为了发送一个信号给一个线程,我们可以调用函数pthread_kill;
#include <signal.h>
int pthread_kill(pthread_t thread, int signl);
Returns: 0 if ok, error number on failure.
我们可以传递给参数signo数值0,用于检测指定线程是否存在。如果一个信号的默认处理方式是终止进程,那么向一个线程发送这个信号也会终止掉整个进程。
注意alarm timers是进程资源,所有的线程都会共享相同的alarms集合,因此,对于一个进程中的多个线程,不可能做到使用alarm timers而不发生冲突。
Example
在图10.23中,我们等待信号处理函数设置一个标志,用于表示主程序可以退出了。在这个程序中,可以运行的程序流仅仅只有主线程以及信号处理函数,所以阻塞信号对于避免丢失标志的改变是有效的。使用多线程以后,我们需要使用互斥锁来保护标志,正如在图12.16中程序所示。
#include "apue.h"
#include <pthread.h>
int quitflag; /* set nonzero by thread */
sigset_t mask;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t waitloc = PTHREAD_COND_INITIALIZER;
void *thr_fn(void *arg)
{
int err, signo;
for(;;)
{
err = sigwait(&mask, &signo);
if(err != 0)
err_exit(err, "sigwait failed");
switch(signo)
{
case SIGINT:
printf("\ninterrupt\n");
break;
case SIGQUIT:
pthread_mutex_lock(&lock);
quitflag = 1;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&waitloc);
return (0);
default:
printf("unexpected signal %d\n", signo);
exit(1);
}
}
}
int main(void)
{
int err;
sigset_t oldmask;
pthread_t tid;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
if((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
err_exit(err, "SIG_BLOCK error");
err = pthread_create(&tid, NULL, thr_fn, 0);
if(err != 0)
{
err_exit(err, "can;t create thread");
}
pthread_mutex_lock(&lock);
while(quitflag == 0)
pthread_cond_wait(&waitloc, &lock);
pthread_mutex_unlock(&lock);
/*SIGQIUT has been caught and is now blocked; do whatever */
quitflag = 0;
/*reset signal mask which unblocks ISGQUIT */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
exit(0);
}
图12.16 同步信号处理
信号处理函数不再会中断主线程,我们使用一个专门的线程来处理信号,我们可以在互斥锁的保护下修改quitflag,so that the main thread of control can’t miss the wake-up call made when we call pthread_cond_signal.我们在主线程中使用同一互斥锁来检查标记的值,并在我们等待条件的时候自动释放锁。
注意同时阻塞了信号SIGINT以及SIGQUIT信号,当我们创建线程来处理信号的时候,线程继承了当前的信号掩码,因为sigwait并不会阻塞信号,因此只有一个线程可以接受信号,这样可以使得我们在编写主线程的时候不用考虑这些中断信号的干扰。
程序运行情况如下入所示:
[email protected]:~/APUE/chapter12$ ./12_16.exe
^C
interrupt
^C
interrupt
^C
interrupt
^\[email protected]:~/APUE/chapter12$
以上是关于12.8 线程和信号的主要内容,如果未能解决你的问题,请参考以下文章