MENU

Linux多线程:线程与信号

每个线程都有自己的信号屏蔽字,新的线程会继承创建它的线程的信号屏蔽字。但是信号处理程序是所有线程共享的,也就是说一个线程注册了一个信号处理程序,其他线程就得使用这个信号处理程序。

Linux线程的信号

进程中的信号是递送到单个线程的。如果信号与硬件故障或记时器超时相关,该信号就被发送到引起该事件的线程中去,而其他信号则被发送到任意一个没有阻塞该信号的线程。

apue中说,注意Linux线程模型和POSIX线程模型的区别,因为Linux中的线程实际上是进程,所以如果我们使用kill函数或命令发送特定的信号到某个进程,在Linux上是特定的线程(主线程)接收到这个信号,而POSIX则是把这个信号发送到任意一个线程。

但是在我电脑测试的结果是,如果用kill命令向主线程发送一个信号,那么这个信号会发送到任意一个线程,如果向非主线程发送信号,那么kill会返回错误“没有那个进程”。

如果用kill函数,则无论向哪个线程发送信号,都会视为向整个进程发送信号(在ulk中说,getpid、kill和_exit这一类系统调用针对整个线程组起整体作用)。

信号屏蔽字

线程的信号屏蔽字用下面的API设置:

#include <pthread.h>
int pthread_sigmask(int how, const sigset_t *restrict nset,
sigset_t *restrict oset);

返回值: 成功返回0,否则返回错误编号

参数:
how参数可以有如下三种取值:

  • SIG_BLOCK:将nset信号集添加到线程信号屏蔽字中

  • SIG_SETMASK:用nset信号集替换线程信号屏蔽字

  • SIG_UNBLOCK:将nset信号集从线程信号屏蔽字中移除

如果oset集不为空的话,在上面三种操作之前会将原线程信号屏蔽字复制到oset,以备后续的还原之用。
如果nset集为NULL,则pthread_sigmask的作用为 ”取线程的信号屏蔽字到 oset 中“

使用示例:

屏蔽SIGTERM、SIGINT、SIGHUP、SIGQUIT四个信号

sigset_t oldset;
sigset_t newset;
sigemptyset(&newset);   /* Clear all signals from SET.  */
    sigaddset(&newset, SIGTERM);    // 终止
    sigaddset(&newset, SIGINT);     // 终端中断符 Delete, Ctrl+C
    sigaddset(&newset, SIGHUP);     // 连接断开
    sigaddset(&newset, SIGQUIT);    // 终端退出符
    pthread_sigmask(SIG_BLOCK, &newset, &oldset);

等待信号

线程可以通过调用sigwait等待一个或多个信号发生:

#include <pthread.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);

返回值: 成功返回0,否则返回错误编号

如果信号集中的某个信号在sigwait调用的时候处于未决状态,那么sigwait将无阻塞地返回,在返回之前,sigwait将从进程中移除那些处于未决状态的信号,sigwait会自动取消信号集的阻塞状态,在返回之前,sigwait将恢复线程的信号屏蔽字。

使用sigwait的好处在于简化信号处理,允许把异步产生的信号用同步的方式处理。为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后安排专用线程作信号处理。这些专用线程可以进行函数调用,不需要担心在信号处理程序中调用哪些函数是安全地,因为这些函数调用来自正常的线程环境,而非传统的信号处理程序,传统的信号处理程序通常会中断线程的正常执行。

当然,sigwait无法让不同的线程用异步处理的方式调用不同的信号处理程序来处理同个信号。

标签: Linux, 线程, 信号