多线程中的信号处理

时间:2023-01-27 14:44:13

  在linux下写服务器,处理信号在所难免。在多线程和单线程中信号的处理还是有点不同的。参考:

http://maxim.int.ru/bookshelf/PthreadsProgram/htm/r_40.html

http://aboocool.blog.51cto.com/3189391/626675

  在linux下,每个进程都有自己的signal mask,这个信号掩码指定哪个信号被阻塞,哪个不会被阻塞,通常用调用sigmask来处理。同时每个进程还有自己的signal action,这个行为集合指定了信号该如何处理,通常调用sigaction来处理。

  使用了多线程后,便有些疑问:

  1. 信号发生时,哪个线程会收到
  2. 是不是每个线程都有自己的mask及action
  3. 每个线程能按自己的方式处理信号么

  首先,信号的传递是根据情况而定的:

多线程中的信号处理

  • 如果是异常产生的信号(比如程序错误,像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:请注意信号阻塞与忽略的区别。