2011年12月1日 星期四

[Pthread] signal handler on other thread

在multithread程式裡面, 不管你是由child thread還是main thread 註冊signal handler, 在signal handler都是在main thread處理.
可以從下面的範例看出


#include
#include
#include
#include

static void hdl (int sig, siginfo_t *siginfo, void *context)
{
pthread_t ret = pthread_self();
sleep(2);
printf ("Sending PID: %ld, UID: %ld, self=%u\n",
(long)siginfo->si_pid, (long)siginfo->si_uid, pthread_self());
}

void *child1_fun(void *data)
{
struct sigaction act;

memset (&act, '\0', sizeof(act));
printf("child1 pid=%u\n", pthread_self());
/* Use the sa_sigaction field because the handles has two additional parameters */
act.sa_sigaction = &hdl;
/* The SA_SIGINFO flag tells sigaction() to use the sa_sigaction field, not sa_handler. */
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGALRM, &act, NULL) < 0) {
perror ("sigaction");
return 1;
}
pause();
}

void *child2_fun(void *data)
{
pthread_t *child1 = (pthread_t *) data;
printf("child2 pid=%u\n", pthread_self());
//alarm(2);
struct itimerval timer;

timer.it_value.tv_sec = 5 ;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 5;
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &timer, NULL);
// pthread_kill(*child1, SIGALRM);
}
int main (int argc, char *argv[])
{
pthread_t child1, child2;
sigset_t newSet;
sigset_t oset;
sigemptyset(&newSet);
sigaddset(&newSet, SIGALRM);
pthread_sigmask(SIG_BLOCK, &newSet, NULL);

printf("main pid=%u\n", pthread_self());
pthread_create(&child1, NULL, child1_fun, NULL);
pthread_create(&child2, NULL, child2_fun, &child1);
pthread_join(child1, NULL);

return 0;
}

可以看到都是main thread在處理signal.
Output:


ytshen@ytshen-ThinkCentre-A58:~/temp$ ./a.out
main pid=256317184
child1 pid=248338176
child2 pid=239945472
Sending PID: 0, UID: 0, self=256317184
Sending PID: 0, UID: 0, self=256317184
^C
ytshen@ytshen-ThinkCentre-A58:~/temp$

當我們希望是尤其他的child thread來處理signal, 我們就必須要改成這樣, 要先在main thread用sigmask 把不想接得signal設成SIG_BLOCK (非常重要! 不然當signal發生他會當你沒有註冊任何signal handler, 造成program stop), 在child thread 呼叫sigwait的時候, 他就會wait你想要wait的signal


#include
#include
#include
#include
#include
#include

void *child1_fun(void *data)
{
sigset_t newSet;
sigemptyset(&newSet);
sigaddset(&newSet, SIGALRM);
int signum;
while(1) {
printf("child1 pid=%u start to wait\n", pthread_self());
sigwait(&newSet, &signum);
printf("child1 pid=%u get signal = %d!\n", pthread_self(), signum);
}
}

void *child2_fun(void *data)
{
pthread_t *child1 = (pthread_t *) data;
printf("child2 pid=%u\n", pthread_self());
//alarm(2);
struct itimerval timer;

timer.it_value.tv_sec = 5 ;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 5;
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &timer, NULL);
// pthread_kill(*child1, SIGALRM);
}

int main (int argc, char *argv[])
{
pthread_t child1, child2;
sigset_t newSet;
sigset_t oset;
sigemptyset(&newSet);
sigaddset(&newSet, SIGALRM);
pthread_sigmask(SIG_BLOCK, &newSet, NULL);

printf("main pid=%u\n", pthread_self());
pthread_create(&child1, NULL, child1_fun, NULL);
pthread_create(&child2, NULL, child2_fun, &child1);
pthread_join(child1, NULL);

}

Output:


ytshen@ytshen-ThinkCentre-A58:~/temp$ ./a.out
main pid=1378113280
child1 pid=1370134272 start to wait
child2 pid=1361741568
child1 pid=1370134272 get signal = 14!
child1 pid=1370134272 start to wait
child1 pid=1370134272 get signal = 14!
child1 pid=1370134272 start to wait
^C
ytshen@ytshen-ThinkCentre-A58:~/temp$

Updated!!
保險作法是在main thread create其他所有thread之前, sigprocmask 把該signal block住, 在create child thread(這樣所有child thread也會繼承main thread signal mask), 然後在要處理signal的child thread去sigwait, 這樣就不會有其他沒有block該signal的child thread因為沒有處理而被terminate!

Reference:
http://www.cognitus.net/html/howto/pthreadSemiFAQ_8.html
http://stackoverflow.com/questions/5282099/signal-handling-in-pthreads
http://blog.csdn.net/wozaiwogu/article/details/4361456
http://blog.csdn.net/fytzzh/article/details/660457