不知道你是否还记得之前在进程中的信号处理时,提到过阻塞信号集与未决信号集的概念,如果你已经忘记了,请参考《阻塞信号与未决信号》一文回忆一下。
1. 多线程程序中的信号
在多线程中,每一个线程都有属于自己的阻塞信号集与未决信号集。当一个线程派生另一个线程的时候,会继承父线程的阻塞信号集,但是不会继承未决信号集,并且新线程会清空未决信号集。
2. 相关函数
2.1 设置阻塞信号集的函数
在多线程程序中,如果要设置线程的阻塞信号集,不能再使用 sigprocmask 函数,而应该使用 pthread_sigmask,其定义如下:
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
这个函数的用法和 sigprocmask 是一样的。
- how 参数
- SIG_BLOCK 该选项表示将 set 参数指示的信号集中的信号添加到进程阻塞集中
- SIG_UNBLOCK 该选项与功能 SIG_BLOCK 相反,表示将线程阻塞信号集中指定的信号删除
- SIG_SETMASK 该选项表示将线程阻塞信号集直接设定为你指定的 set
- set 参数
表示你指定的信号集合
- oldset
返回旧的阻塞信号集
- 返回值 int
0 表示成功,-1 失败。
2.2 获取未决信号的函数
该函数仍然是 sigpending,没有变化。它的原型如下:
int sigpending(sigset_t *set);
2.3 信号发送函数
kill 函数只能给指定的进程发送函数,而是使用 pthread_kill 可以给指定的线程发送函数。它的原型如下:
int pthread_kill(pthread_t thread, int sig);
3. 实验
程序 th_sig 做了下面几个工作:
- 在主线程阻塞了 SIGQUIT 信号
- 在 fun1 线程中阻塞了 SIGINT 信号
- 在 fun2 线程中什么也没阻塞
3.1 代码
// th_sig.c
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
// 打印信号集
void printsigset(const sigset_t *set) {
int i;
for (i = 1; i <= 64; i++) {
if (i==33) putchar(' ');
if (sigismember(set, i) == 1)
putchar('1');
else
putchar('0');
}
puts("");
}
// fun1 线程
void* fun1(void* arg) {
// 阻塞 SIGINT 信号
sigset_t mask, st;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
while(1) {
printf("I'm fun1:\t");
sigpending(&st);
printsigset(&st);
sleep(3);
}
}
// fun2 线程
void* fun2(void* arg) {
sigset_t st;
while(1) {
printf("I'm fun2:\t");
sigpending(&st);
printsigset(&st);
sleep(3);
}
}
int main() {
// 创建线程前阻塞 SIGQUIT
sigset_t mask, st;
sigemptyset(&mask);
sigaddset(&mask, SIGQUIT);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, fun1, NULL);
pthread_create(&tid2, NULL, fun2, NULL);
sleep(2);
pthread_kill(tid1, SIGINT);
while(1) {
printf("I'm main:\t");
sigpending(&st);
printsigset(&st);
sleep(3);
}
return 0;
}
3.2 编译与运行
- 编译
$ gcc th_sig.c -o th_sig -lpthread
- 运行
图1 运行结果
关于图 1 的说明:左侧是线程的名字,右侧是打印的未决队列,也就是未被信号处理函数处理的信号。其中第 1 列是 SIGHUP 信号,第 2 列表示 SIGINT 信号,第 3 列表示 SIGQUIT 信号。
当主线程休眠 2 秒后,给线程 fun1 发送了一个 SIGINT 信号,此时 fun1 的未决信号集中第 2 列变成了 1 (图 1 上的第 5 行)。
在后面某个时候,按下了 CTRL + \,表示发送信号 SIGQUIT 给前台进程组中的进程。接下来,可以发现,所有的线程未决信号集中的第 3 列都变成了 1. 这说明了两件事:
- 线程继承了父线程阻塞信号集
- kill 信号会发送给进程中的所有线程
4. 总结
- 每个线程有自己的阻塞信号集与未决信号集
- 线程会继承父线程的阻塞信号集,新线程会清空未决信号集
- pthread_kill 发送信号给指定线程
- kill 发送信号给所有线程