多线程下信号处理
现在做一个测试:
假设:由父线程发送两个特定的信号到进程id,然后我们去查看线程接受信号的状态,看看会发生什么。
备注:
处理信号的线程: 只是调用sigwait()阻塞地等待信号的线程。
多个子线程: worker thread.
通过控制变量,可以观察到以下情况:
-
父线程与多个子线程
父线程和子线程均不屏蔽信号。
现象:一旦父线程向进程id发送信号,似乎总是先被父线程处理信号。其他线程无法收到信号。 -
父线程 + 一个信号处理线程 + 多个子线程(近水楼台先得月)
父线程、子线程和处理线程均不屏蔽信号。
现象:一旦父线程向进程id发送信号,似乎总是先被父线程处理信号。其他线程无法收到信号。 -
一个信号处理线程+多个子线程
父线程屏蔽信号。
现象:一旦父线程向进程id发送信号,信号似乎总是先被处理线程接受。但其他线程偶尔也能收到信号。
使用似乎,从现象上看,接受信号的优先级似乎是 信号发送的线程 > 处理信号的线程 > 其他子线程,但实际上,只要线程没有屏蔽信号,都有可能接受到相关的信号。
测试代码: 读者可根据控制条件作相应的改变。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
using namespace std;
void sig_handler(int signum)
{
static int j = 0;
static int k = 0;
pthread_t sig_ppid = pthread_self();
// used to show which thread the signal is handled in.
if (signum == SIGUSR1) {
printf("thread %u, receive SIGUSR1 No. %d\n", sig_ppid, j);
j++;
//SIGRTMIN should not be considered constants from userland,
//there is compile error when use switch case
} else if (signum == SIGRTMIN) {
printf("thread %u, receive SIGRTMIN No. %d\n", sig_ppid, k);
k++;
}
}
void* worker_thread(void *)
{
pthread_t ppid = pthread_self();
pthread_detach(ppid);
while (1) {
printf("I'm worker thread %u, I'm alive\n", ppid);
sleep(10);
}
}
void* sigmgr_thread(void *)
{
sigset_t waitset, oset;
siginfo_t info;
int rc;
pthread_t ppid = pthread_self();
pthread_detach(ppid);
printf("I am signal handle thread id: %u\n", ppid);
sigemptyset(&waitset);
sigaddset(&waitset, SIGRTMIN);
sigaddset(&waitset, SIGUSR1);
// 测试使用,模拟繁重的初始任务
sleep(5);
while (1) {
rc = sigwaitinfo(&waitset, &info);
if (rc != -1) {
printf("sigwaitinfo() fetch the signal - %d\n", rc);
sig_handler(info.si_signo);
} else {
printf("sigwaitinfo() returned err: %d; %s\n", errno, strerror(errno));
}
}
}
int main()
{
sigset_t bset, oset;
int i;
pid_t pid = getpid();
pthread_t ppid;
signal(SIGUSR1, sig_handler);
signal(SIGRTMIN, sig_handler);
printf("main thread id: %u\n", pthread_self());
if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
printf("!! Set pthread mask failed\n");
sigemptyset(&bset);
sigaddset(&bset, SIGRTMIN);
sigaddset(&bset, SIGUSR1);
// SIGUSR1 and SIGRTMIN synchronously
pthread_create(&ppid, NULL, sigmgr_thread, NULL);
// 创建5个工作线程
for (i = 0; i < 5; i++) {
pthread_create(&ppid, NULL, worker_thread, NULL);
}
// 屏蔽父线程对SIGUSR1、SIGRTMIN的接收
if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
printf("!! Set pthread mask failed\n");
// send out 50 SIGUSR1 and SIGRTMIN signals
for (i = 0; i < 50; i++) {
kill(pid, SIGUSR1);
printf("main thread, send SIGUSR1 No. %d\n", i);
kill(pid, SIGRTMIN);
printf("main thread, send SIGRTMIN No. %d\n", i);
sleep(10);
}
exit (0);
}
小总结
通过上面我们可以看出信号处理在多线程下有以下特点:
1.向进程id发出的信号,所有线程都能收到,但信号就像水管里的水一样,被不同线程依次获取,不是所有线程都能收到该信号。
2.子线程会继承父线程的信号屏蔽集与处理函数
3.各线程间的执行是独立,不受信号处理的影响,也就是说信号的到达不会将所有线程都中断。
信号安全的定义?
异步信号安全(async-signal-safe)
在SIGNAL-SAFETY(7)中是这么提及
异步信号安全函数可以在signal handler中安全地调用。但绝大多数函数都是非异步信号安全的。特别的,不可重入函数普遍都不能在signal handler中安全地调用
一个函数是异步信号安全的,要么是因为它是可重入的,或是因为它在信号方面是原子的。(例如,不会被signal handler中断)
可重入(reentrant)
多次调用一个函数,不会因为调用顺序的不同,造成不同的结果。因而,多数维护一个全局变量的函数都不是可重入的。因为不同的调用顺序可能会导致全局状态被破坏,从产生非预期结果。