linux多线程信号处理
在linux下,每个进程都有自己的signal mask,这个信号掩码指定哪个信号被阻塞,哪个不会被阻塞,通常用调用sigmask来处理。同时每个进程还有自己的signal action,这个行为集合指定了信号该如何处理,通常调用sigaction来处理。
使用了多线程后,便有些疑问:
信号发生时,哪个线程会收到
是不是每个线程都有自己的mask及action
每个线程能按自己的方式处理信号么
首先,信号的传递是根据情况而定的:
如果是异常产生的信号(比如程序错误,像SIGPIPE、SIGEGV这些),则只有产生异常的线程收到并处理。
如果是用pthread_kill产生的内部信号,则只有pthread_kill参数中指定的目标线程收到并处理。
如果是外部使用kill命令产生的信号,通常是SIGINT、SIGHUP等job control信号,则会遍历所有线程,直到找到一个不阻塞该信号的线程,然后调用它来处理。(一般从主线程找起),注意只有一个线程能收到。
其次,每个线程都有自己独立的signal mask,但所有线程共享进程的signal action。这意味着,你可以在线程中调用pthread_sigmask(不是sigmask)来决定本线程阻塞哪些信号。但你不能调用sigaction来指定单个线程的信号处理方式。如果在某个线程中调用了sigaction处理某个信号,那么这个进程中的未阻塞这个信号的线程在收到这个信号都会按同一种方式处理这个信号。另外,注意子线程的mask是会从主线程继承而来的。
第三个问题,因为signal action共享的问题,已经知道不能。
下面以一个例子说明:
/*threadsig.c*/
#include <signal.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void sighandler(int signo);
void *
thr1_fn(void *arg)
{
struct sigaction action;
action.sa_flags = 0;
action.sa_handler = sighandler;
sigaction(SIGINT, &action, NULL);
pthread_t tid = pthread_self();
int rc;
printf("thread 1 with tid:%lu\n", tid);
rc = sleep(60);
if (rc != 0)
printf("thread 1... interrupted at %d second\n", 60 - rc);
printf("thread 1 ends\n");
return NULL;
}
void *
thr2_fn(void *arg)
{
struct sigaction action;
pthread_t tid = pthread_self();
int rc, err;
printf("thread 2 with tid:%lu\n", tid);
action.sa_flags = 0;
action.sa_handler = sighandler;
err = sigaction(SIGALRM, &action, NULL);
rc = sleep(60);
if (rc != 0)
printf("thread 2... interrupted at %d second\n", 60 - rc);
printf("thread 2 ends\n");
return NULL;
}
void *
thr3_fn(void *arg)
{
pthread_t tid = pthread_self();
sigset_t mask;
int rc, err;
printf("thread 3 with tid%lu\n", tid);
sigemptyset(&mask); /* 初始化mask信号集 */
sigaddset(&mask, SIGALRM);
err = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (err != 0)
{
printf("%d, %s/n", rc, strerror(rc));
return NULL;
}
rc = sleep(10);
if (rc != 0)
printf("thread 3... interrupted at %d second\n", 60 - rc);
err = pthread_sigmask( SIG_UNBLOCK,&mask,NULL );
if ( err != 0 )
{
printf("unblock %d, %s/n", rc, strerror(rc));
return NULL;
}
rc = sleep(10);
if (rc != 0)
printf("thread 3... interrupted at %d second after unblock\n", 60 - rc);
printf("thread 3 ends\n");
return NULL;
return NULL;
}
int
main(void)
{
int rc, err;
pthread_t thr1, thr2, thr3, thrm = pthread_self();
printf("thread main with pid %lu\n",thrm);
err = pthread_create(&thr1, NULL, thr1_fn, NULL);
if (err != 0) {
printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
exit(1);
}
/* pthread_kill(thr1, SIGALRM); send a SIGARLM signal to thr1 before thr2 set the signal handler, then the whole process will be terminated*/
err = pthread_create(&thr2, NULL, thr2_fn, NULL);
if (err != 0) {
printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
exit(1);
}
err = pthread_create(&thr3, NULL, thr3_fn, NULL);
if (err != 0) {
printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
exit(1);
}
sleep(10);
//内部产生的信号,只有指定的线程能收到,因此要向所有线程发送
pthread_kill(thr1, SIGALRM);
pthread_kill(thr2, SIGALRM);
pthread_kill(thr3, SIGALRM);
pthread_kill(thr3, SIGALRM);
pthread_kill(thr3, SIGALRM);
sleep(5);
pthread_join(thr1, NULL); /*wait for the threads to complete.*/
pthread_join(thr2, NULL);
pthread_join(thr3, NULL);
printf("main ends\n");
return 0;
}
void
sighandler(int signo)
{
pthread_t tid = pthread_self();
printf("thread with pid:%lu receive signo:%d\n", tid, signo);
return;
}
在上面的代码中,主线程创建三个线程。线程1注册SIGINT信号(即ctrl+c) ,线程2注册SIGALRM,线程三则是先阻塞SIGALRM,然后解除阻塞。
编译后看运行结果:
xzc@xzc-HP-ProBook-4446s:~/code/test$ gcc -o threadsig threadsig.c -pthread
xzc@xzc-HP-ProBook-4446s:~/code/test$ ./threadsig
thread main with pid 139946922108736
thread 2 with tid:139946905396992
thread 1 with tid:139946913789696
thread 3 with tid139946897004288
^Cthread with pid:139946922108736 receive signo:2
thread with pid:139946913789696 receive signo:14
thread 1... interrupted at 4 second
thread 1 ends
thread with pid:139946905396992 receive signo:14
thread 2... interrupted at 4 second
thread 2 ends
^Cthread with pid:139946922108736 receive signo:2
^Cthread with pid:139946922108736 receive signo:2
thread with pid:139946897004288 receive signo:14
thread 3 ends
main ends
xzc@xzc-HP-ProBook-4446s:~/code/test$
在第一行红色的地方,主线程正在sleep,我按下ctrl+c,只有主线程收到并处理了信号。说明进程会从主线程开始查找不阻塞该信号的线程来处理job control类的信号。
由于主线程sleep被打断,随后向三个线程发送了SIGALRM,线程1、2由于没有阻塞该信号,被迫从sleep中醒来,并结束进程。进程3仍在sleep。
在第二行红色的地方,线程3第一次sleep终于完成,解除了对SIGALRM的阻塞。于是马上收到被阻塞的SIGALRM(发送3次,只收到一次)。PS:请注意信号阻塞与忽略的区别。
pthread & signal
pthread线程和信号
所有的异步信号发到整个进程的所有线程(异步信号如kill, lwp_kill, sigsend, kill等调用产生的都是,异步信号也称为中断),而且所有线程共享信号的处理行为(即sigaction的设置,对于同一信号的设置,某一线程的更改会影响到所有线程)。但每个线程可以有自己的mask来阻止信号的发送,所以可以通过线程对mask的设置来决定信号发送到哪个线程。设置mask的函数为:
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset)
此外,线程可以通过sleep(超过指定时间或调用的进程/线程捕捉到某个信号并从信号处理程序返回时,sleep返回)或者sigwait来等待一个或多个信号发生。
#include <signal.h>
int pthread_sigwait(const sigset_t *restrict set, int *restrict signop);
给进程发送信号可以调用kill,同样给线程调用信号可以使用pthread_kill
#include <signal.h>
int pthread_kill(pthread_t thread, int signo);
可以发送一个0的signo来检查线程是否存在,如果信号的默认行为是终止进程(例如SIGARLM),那么把该信号发送给某个线程会杀掉整个进程的所有线程。
另外注意ALARM是进程资源,并且所有线程共享相同的ALARM,设置一个alarm()会发送SIGARLM信号给所有线程,所以他们不可能互补干扰的使用alarm()。
here comes an example:
/*threadsig.c*/
#include <signal.h>
#include <pthread.h>
#include <stdio.h>
void sighandler(int signo);
void *
thr1_fn(void *arg)
{
pthread_t tid = pthread_self();
int rc;
printf("thread 1 with tid:%u\n", tid);
rc = sleep(60);
if (rc != 0)
printf("thread 1... interrupted at %d second\n", 60 - rc);
printf("thread 1 ends\n");
return NULL;
}
void *
thr2_fn(void *arg)
{
struct sigaction action;
pthread_t tid = pthread_self();
int rc, err;
printf("thread 2 with tid:%u\n", tid);
action.sa_flags = 0;
action.sa_handler = sighandler;
err = sigaction(SIGALRM, &action, NULL);
rc = sleep(60);
if (rc != 0)
printf("thread 2... interrupted at %d second\n", 60 - rc);
printf("thread 2 ends\n");
return NULL;
}
void *
thr3_fn(void *arg)
{
pthread_t tid = pthread_self();
sigset_t mask;
int rc, err;
printf("thread 3 with tid%u\n", tid);
sigemptyset(&mask); /* 初始化mask信号集 */
sigaddset(&mask, SIGALRM);
err = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (err != 0)
{
printf("%d, %s/n", rc, strerror(rc));
return NULL;
}
rc = sleep(60);
if (rc != 0)
printf("thread 3... interrupted at %d second\n", 60 - rc);
printf("thread 3 ends\n");
return NULL;
return NULL;
}
int
main(void)
{
int rc, err;
pthread_t thr1, thr2, thr3, thrm = pthread_self();
printf("thread main with pid %u\n", (unsigned int)thrm);
err = pthread_create(&thr1, NULL, thr1_fn, NULL);
if (err != 0) {
printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
exit(1);
}
/* pthread_kill(thr1, SIGALRM); send a SIGARLM signal to thr1 before thr2 set the signal handler, then the whole process will be terminated*/
err = pthread_create(&thr2, NULL, thr2_fn, NULL);
if (err != 0) {
printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
exit(1);
}
err = pthread_create(&thr3, NULL, thr3_fn, NULL);
if (err != 0) {
printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
exit(1);
}
sleep(3);
pthread_kill(thr1, SIGALRM);
pthread_kill(thr2, SIGALRM);
pthread_kill(thr3, SIGALRM);
pthread_join(thr1, NULL); /*wait for the threads to complete.*/
pthread_join(thr2, NULL);
pthread_join(thr3, NULL);
printf("main ends\n");
return 0;
}
void
sighandler(int signo)
{
pthread_t tid = pthread_self();
printf("thread with pid:%u receive signo:%d\n", tid, signo);
return;
}
luffy@luffy-laptop:~/workspace/myapue$ ./threadsig
thread main with pid 3557979936
thread 1 with tid:3549923072
thread 2 with tid:3541530368
thread 3 with tid3533137664
thread with pid:3549923072 receive signo:14
thread with pid:3541530368 receive signo:14
thread 2... interrupted at 3 second
thread 1... interrupted at 3 second
thread 1 ends
thread 2 ends #then wait for 27 seconds and thread-3 ends
thread 3 ends
main ends
thr2设置的信号处理程序sighandler也应用到其他线程,thr3由于设置mask所有阻塞了SIGARLM信号。
Reference:
APUE
Linux线程信号
1. 概念
按照 POSIX, 异步 (外部) 信号发送到整个进程.
所有线程共享同一个设置, 即通过 sigaction 设置的线程处置方法.
每个线程有自己的信号掩码, 线程库根据该掩码决定将信号发送到哪个线程.
由于Linux 线程实现上的独特性, 外部信号始终发送到特定的线程.
2. 例子
#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#define NUMTHREADS 3
void sighand(int signo);
void *threadfunc(void *parm)
{
pthread_t tid = pthread_self();
int rc;
printf("Thread %u entered/n", tid);
rc = sleep(30); /* 若有信号中断则返回剩余秒数 */
printf("Thread %u did not get expected results! rc=%d/n", tid, rc);
return NULL;
}
void *threadmasked(void *parm)
{
pthread_t tid = pthread_self();
sigset_t mask;
int rc;
printf("Masked thread %lu entered/n", tid);
sigfillset(&mask); /* 将所有信号加入mask信号集 */
/* 向当前的信号掩码中添加mask信号集 */
rc = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (rc != 0)
{
printf("%d, %s/n", rc, strerror(rc));
return NULL;
}
rc = sleep(15);
if (rc != 0)
{
printf("Masked thread %lu did not get expected results! rc=%d /n", tid, rc);
return NULL;
}
printf("Masked thread %lu completed masked work/n", tid);
return NULL;
}
int main(int argc, char **argv)
{
int rc;
int i;
struct sigaction actions;
pthread_t threads[NUMTHREADS];
pthread_t maskedthreads[NUMTHREADS];
printf("Enter Testcase - %s/n", argv[0]);
printf("Set up the alarm handler for the process/n");
memset(&actions, 0, sizeof(actions));
sigemptyset(&actions.sa_mask); /* 将参数set信号集初始化并清空 */
actions.sa_flags = 0;
actions.sa_handler = sighand;
/* 设置SIGALRM的处理函数 */
rc = sigaction(SIGALRM,&actions,NULL);
printf("Create masked and unmasked threads/n");
for(i=0; i<NUMTHREADS; ++i)
{
rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
if (rc != 0)
{
printf("%d, %s/n", rc, strerror(rc));
return -1;
}
rc = pthread_create(&maskedthreads[i], NULL, threadmasked, NULL);
if (rc != 0)
{
printf("%d, %s/n", rc, strerror(rc));
return -1;
}
}
sleep(3);
printf("Send a signal to masked and unmasked threads/n");
/* 向线程发送SIGALRM信号 */
for(i=0; i<NUMTHREADS; ++i)
{
rc = pthread_kill(threads[i], SIGALRM);
rc = pthread_kill(maskedthreads[i], SIGALRM);
}
printf("Wait for masked and unmasked threads to complete/n");
for(i=0; i<NUMTHREADS; ++i) {
rc = pthread_join(threads[i], NULL);
rc = pthread_join(maskedthreads[i], NULL);
}
printf("Main completed/n");
return 0;
}
void sighand(int signo)
{
pthread_t tid = pthread_self();
printf("Thread %lu in signal handler/n", tid);
return;
}
3. 打印结果
Enter Testcase - ./test
Set up the alarm handler for the process
Create masked and unmasked threads
Thread 3085065104 entered
Masked thread 3076672400 entered
Thread 3068279696 entered
Masked thread 3059886992 entered
Thread 3051494288 entered
Masked thread 3043101584 entered
Send a signal to masked and unmasked threads
Thread 3085065104 in signal handler
Thread 3085065104 did not get expected results! rc=27
Thread 3068279696 in signal handler
Thread 3068279696 did not get expected results! rc=27
Thread 3051494288 in signal handler
Thread 3051494288 did not get expected results! rc=27
Wait for masked and unmasked threads to complete
Masked thread 3076672400 completed masked work
Masked thread 3059886992 completed masked work
Masked thread 3043101584 completed masked work
Main completed
4. 相关函数
sigaction(查询或设置信号处理方式)
#include<signal.h>
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。
如参数结构sigaction定义如下
struct sigaction
{
void (*sa_handler) (int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
}
sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数,其他意义请参考signal()。
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置。
sa_restorer 此参数没有使用。
sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
sigfillset(将所有信号加入此信号集)
#include<signal.h>
int sigfillset(sigset_t * set);
sigfillset()用来将参数set信号集初始化,然后把所有的信号加入到此信号集里。
sigemptyset(初始化信号集)
#include<signal.h>
int sigemptyset(sigset_t *set);
sigemptyset()用来将参数set信号集初始化并清空。
pthread_sigmask(更改或检查调用线程的信号掩码)
#include <pthread.h>
#include<signal.h>
int pthread_sigmask(int how, const sigset_t *new, sigset_t *old);
how用来确定如何更改信号组,可以为以下值之一:
SIG_BLOCK:向当前的信号掩码中添加new,其中new表示要阻塞的信号组。
SIG_UNBLOCK:向当前的信号掩码中删除new,其中new表示要取消阻塞的信号组。
SIG_SETMASK:将当前的信号掩码替换为new,其中new表示新的信号掩码。
pthread_kill(向线程发送信号)
#include <pthread.h>
#include<signal.h>
int pthread_kill(thread_t tid, int sig);
pthread_kill()将信号sig发送到由tid指定的线程。tid所指定的县城必须与调用线程在同一个进程中。
更多推荐
所有评论(0)