分类:
2008-12-16 10:22:50
12.8 thread and
signals
(一)基础
Signal本身使进程较为复杂,在进城中加入了thread后,就更复杂。
1.每个thread都有自己的signal mask,这是从main thread继承过来的。如果main thread将一些信号给block了,那么之后生成的thread也会将这些信号给block。个人测试得出:在thread里,调用pthread_sigmask和sigprocmask的效果是一样的。在一个thread里面更改signal mask只会影响本thread。
2.Thread之间共享signal的handler,即他们没有各自的handler。一个thread修改了一个signal的handler,会影响到别的thread的handler。
3.在我的suse机子上,各个thread的pid是一样的。
(二)信号分配原则:
1.如果一个thread触发了hardware
fault,或者expired timer,signal会发送给这个thread,否则其他的信号就发给其进程,在进程的所有thread中选择一个thread来接受该信号。
2.在thread之间进行选择的时候,会选择当前没有将该信号block的thread。如果有多个thread没有block该信号,那么取决于implementation。
3.书上说,linux的各个thread是通过clone实现的,其本质是独立的进程,只不过共享了数据,所以 各个thread的getpid的返回结果也不一样。因此你如果靠kill发送一个信号给一个进程,妄图在该进程的各个thread中选择是不行的,因为你仅仅是将信号发给了该进程,其他进程并没有受到这个信号, linux不能在其他进程里选择一个进程来处理该信号。但如果你是使用controllin
terminal发送的信号,由于它会将信号发送给一个process group,所以接受进程及其threads(其实也是进程)都会受到信号,那么就可以依据哪个thread将信号unblock了而选择哪个thread来处理该信号。
然而实际上,在我的测试环境里:一个进程建立的thread的pid与其父进程是一样的。并且,所以,我只能向该进程的pid,发送信号,而且,我使用kill -USR1
pid来发送信号,给该进程,该进程可以在它的若干个thread里面选择一个来对本信号进行响应,注意,只选择一个thread响应一次。这样看来,我现在使用的linux版本以及pthread库已经使他的thread和signal的语义与标准的posix语义保持了一致了。
下面是我使用的测试代码(使用sigprocmask/pthread_sigmask)是等效的:
#include
#include
#include
#include
#include
void handler( int signo )
{
pthread_t
t = pthread_self();
printf("Handler
called in thread %d\n", (unsigned long)t);
}
void* thread( void* para )
{
pthread_t
t = pthread_self();
printf("thread
%d, pid:%d \n", (unsigned long)t, getpid() );
printf("para
== %d\n", *((int*)para));
//我们靠参数para取值的不同来讲其中一个thread的SIGUSR1给unblock掉。
if(
*((int*)para) == 1 )
{
sigset_t
newset, oldset;
sigemptyset(&newset);
sigaddset(&newset,
SIGUSR1);
printf("thread
%d unblocks SIGUSR1\n", t);
pthread_sigmask(
SIG_UNBLOCK, &newset, NULL );
sigprocmask(
SIG_UNBLOCK, &newset, NULL );
}
sleep(30);
printf("thread
%d waked up\n", (unsigned long)t);
pthread_exit(0);
}
int main()
{
pid_t
pid = getpid();
printf("main
thread pid:%d\n", pid );
struct
sigaction oldact, newact;
newact.sa_handler
= handler;
if(
sigaction( SIGUSR1, &newact, &oldact )<0 )
exit(1);
//main
thread block SIGUSR1
sigset_t
newset, oldset;
sigemptyset(&newset);
sigaddset(&newset,
SIGUSR1);
printf("main
thread will block SIGUSR1\n");
// sigprocmask(
SIG_BLOCK, &newset, &oldset);
pthread_sigmask(
SIG_BLOCK, &newset, &oldset);
//create
threads
pthread_t
t1,t2;
void
*st1, *st2;
int
para0 = 0, para1 = 1;
if(
pthread_create(&t1, NULL, thread, (void*)¶0)<0 )
{
puts("thread
create error");
exit(1);
}
if(
pthread_create( &t2, NULL, thread, (void*)(¶1))<0 )
{
puts("thread
create error");
exit(1);
}
if(
pthread_join( t1, &st1 )<0 )
{
puts("thread
1 wait error");
exit(1);
}
if(
pthread_join( t2, &st2 )<0 )
{
puts("thread
2 wait error");
exit(1);
}
printf("thread
1 returns %d\n", st1 );
printf("thread
2 returns %d\n", st2 );
// sigprocmask(
SIG_SETMASK, &oldset, NULL );
pthread_sigmask(
SIG_SETMASK, &oldset, NULL );
return
0;
}
结果正如我们预料:
[shaoting@serverbj6:~]$ main thread pid:26790
main thread will block SIGUSR1
thread 1084229952, pid:26790
para == 0
thread 1094719808, pid:26790
para == 1
thread 1094719808 unblocks SIGUSR1
kill -USR1 26790
[shaoting@serverbj6:~]$ Handler called in thread
1094719808
thread 1094719808 waked up
thread 1084229952 waked up
thread 1 returns 0
thread 2 returns 0
[1]+
Done ./a.out
1.我们的3个thread的pid都是26790
2.首先,main thread将SIGUSR1 block了,然后thread 1084229952同样也是继承了main thread的特性,默认将其block, 而thread 1094719808手动unblock了SIGUSR1
3. 我们发送SIGUSR1信号到该进程,导致了thread 1094719808被打断,handler被调用,在handler内部调用pthread_self得到的结果是1094719808。
(三)sigwait函数
这个函数可以让你的thread以指定的signal mask进入block状态,等待某些信号的发生,当该信号发生后,会将该信号值得到,并且该信号的handler并不被调用。即signal handler并不会打断我们的操作,并且我们可以捕获到该信号。其意义在于将异步的信号捕捉转换为同步的捕捉。我们可以建立一堆threads, 其他工作thread将一些信号全都给block,只让一个thread 将他们unblock,这样就只有这一个thread能够接受到该信号,这个thread使用sigwait函数等待信号的到来,等到来之后,在执行一些操作,我们调用这些操作的时候,并不是在signal
handler里面,所以不会有async-signal
safe的问题,所以我们只需要保证thread safe就可以了。很好。
原文:
The advantage to using sigwait is that it can simplify signal handling by allowing us to treat asynchronously-generated signals in a synchronous manner. We can prevent the signals from interrupting the threads by adding them to each thread's signal mask. Then we can dedicate specific threads to handling the signals. These dedicated threads can make function calls without having to worry about which functions are safe to call from a signal handler, because they are being called from normal thread context, not from a traditional signal handler interrupting a normal thread's execution.
下面就是书中使用这种机制的代码:
#include "apue.h"
#include
int quitflag; /* set nonzero by thread */
sigset_t mask;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t wait = PTHREAD_COND_INITIALIZER;
void *
thr_fn(void *arg)
{
int err, signo;
for (;;) {
err = sigwait(&mask, &signo);
if (err != 0)
err_exit(err, "sigwait failed");
switch (signo) {
case SIGINT:
printf("\ninterrupt\n");
break;
case SIGQUIT:
pthread_mutex_lock(&lock);
quitflag = 1;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&wait);
return(0);
default:
printf("unexpected signal %d\n", signo);
exit(1);
}
}
}
int
main(void)
{
int err;
sigset_t oldmask;
pthread_t tid;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
err_exit(err, "SIG_BLOCK error");
err = pthread_create(&tid, NULL, thr_fn, 0);
if (err != 0)
err_exit(err, "can't create thread");
pthread_mutex_lock(&lock);
while (quitflag == 0)
pthread_cond_wait(&wait, &lock);
pthread_mutex_unlock(&lock);
/* SIGQUIT has been caught and is now blocked; do whatever */
quitflag = 0;
/* reset signal mask which unblocks SIGQUIT */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
exit(0);
}
#include int sigwait(const sigset_t *restrict set, int *restrict signop); |
Returns: 0 if OK, error number on failure |
#include int pthread_sigmask(int how, const sigset_t *restrict set,
sigset_t *restrict oset); |
Returns: 0 if OK, error number on failure |
#include int pthread_kill(pthread_t thread, int signo); |
Returns: 0 if OK, error number on failure |
用这个函数可以向一个thread发送信号,当然只是在一个进程内部。