Linux 多线程编程 与 信号处理
原来在main函数中把几个子线程启动后就睡10分钟后开始清理子线程后退出。现在想改成子线程启动后主线程进入无限睡眠,直到收到SIGTERM或SIGINT。主程序如下:其他头文件#include//信号处理所需要的头文件int main(int argc, char * argv[]){ //其他所需要的变量声明 sigset_t sig_set,sig_pend
·
原来在main函数中把几个子线程启动后就睡10分钟后开始清理子线程后退出。现在想改成子线程启动后主线程进入无限睡眠,直到收到SIGTERM或SIGINT。主程序如下:
其他头文件
#include <signal.h> //信号处理所需要的头文件
int main(int argc, char * argv[]){
//其他所需要的变量声明
sigset_t sig_set,sig_pending;
// 设置信号阻塞
sigemptyset(&sig_set);
sigaddset(&sig_set,SIGTERM);
sigaddset(&sig_set,SIGINT);
sigprocmask(SIG_BLOCK,&sig_set,NULL);
启动几个子线程
...........
// 设置信号阻塞
sigemptyset(&sig_set);
sigaddset(&sig_set,SIGTERM);
sigaddset(&sig_set,SIGINT);
sigprocmask(SIG_BLOCK,&sig_set,NULL);
//主线程进入睡眠,等待信号到达后跳出睡眠
while(1){
sigpending(&sig_pending);
if(sigismember(&sig_pending, SIGTERM)||
sigismember(&sig_pending,SIGINT)){
break;
}
sleep(2);
}
//子线程退出情理
................
return 0;
}
程序运行后发现 当按下Ctrl+C后程序没有出现子线程退出时的信息而是立刻退出,非常奇怪。
仔细分析了一下,发现问题在于忽略了Linux下的多线程模型的特点。
Linux下的线程实质上是轻量级进程(light weighted process), 线程生成时会生成对应的进程控制结构,只是该结构与父线程的进程控制结构共享了同一个进程内存空间。 同时新线程的进程控制结构将从父线程(进程)处复制得到同样的进程信息,如打开文件列表和信号阻塞掩码等 。
由于我们是在子线程生成之后修改了信号阻塞掩码,此刻子线程使用的是主线程原有的进程信息,因此子线程仍然会对SIGINT和SIGTERM信号进行反应
,因此当我们用Ctrl+C发出了SIGINT信号的时候,主进程不处理该信号,而子进程(线程)会进行默认处理,即退出。
子进程退出的同时会向父进程(线程)发送SIGCHLD信号,表示子进程退出,由于该信号没有被阻塞,因此会导致主进程(线程)也立刻退出,出现了前述的运行情况。
因而该问题的一个解决方法是:
1:在子线程生成前进行信号设置,
2:或在子线程内部进行信号设置。
由于子线程是往往是一个事务处理函数,因此我建议在简单的情况下采用前者,如果需要处理的信号比较复杂,那就必须使用后一种方法来处理。这样,以上的程序逻辑改为如下就可以了:
#include <signal.h> //信号处理所需要的头文件
#include <signal.h> //信号处理所需要的头文件
int main(int argc, char * argv[]){
//其他所需要的变量声明
sigset_t sig_set,sig_pending;
//其他所需要的变量声明
sigset_t sig_set,sig_pending;
启动几个子线程
...........
//主线程进入睡眠,等待信号到达后跳出睡眠
while(1){
sigpending(&sig_pending);
if(sigismember(&sig_pending, SIGTERM)||
sigismember(&sig_pending,SIGINT)){
break;
}
sleep(2);
}
//子线程退出情理
................
return 0;
}
...........
//主线程进入睡眠,等待信号到达后跳出睡眠
while(1){
sigpending(&sig_pending);
if(sigismember(&sig_pending, SIGTERM)||
sigismember(&sig_pending,SIGINT)){
break;
}
sleep(2);
}
//子线程退出情理
................
return 0;
}
#################################
多线程下安全的信号处理
最近项目上出现了一个小问题,今天把这个问题修补了一下。为此在博客上也做个记录。
最近项目上出现了一个小问题,今天把这个问题修补了一下。为此在博客上也做个记录。
问题是有关端口绑定引起的,原来我们在做测试的时候,一般用ctrl+c退出server程序,这样退出有一个问题。
上次bind的端口仍然被占用着,如果马上重新执行server程序,那么会出现"bind listening sockey falure!"的情况,这样的话要等待一会儿,才能重新运行程序
。为此我修改了一下代码,在里面添加了一个专门用来捕获SIGINT信号的线程,用这个线程来处理信号,回收资源。具体的代码如下:
v_int_32 start()
{
v_int_32 ret = 0;
v_int_32 created_threads = 0;
sigset_t bset, oset;
sigemptyset(&bset);
sigaddset(&bset, SIGINT);//建立一个信号集,将SIGINT添加进去
if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0) {//设置父线程的信号掩码,子线程会继承这信号掩码
printf("set thread signal mask failrue!/n");
}
ret = pthread_create(&receive_thread_, NULL, receive_packet, (void*)analysis_queue_);
if (ret != 0)
{
//error, destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
pthread_rwlock_destroy(&ip_list_lock);
return ERROR_THREAD_CREATE;
}
usleep(INIT_TIME);
for (v_int_32 j=0; j<ANALYSE_THREAD_MAX; j++)
{
ret = pthread_create(&analyze_thread_[j], NULL, analysis_packet, (void*)analysis_queue_[j]);
if (ret != 0)
{
break;
}
created_threads++;
usleep(INIT_TIME); //To ensure the binding to cpu is finished
}
ret = pthread_create(&signal_handle_thread_, NULL, handle_signal, NULL);
if (ret != 0)
{
//error, destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
pthread_rwlock_destroy(&ip_list_lock);
return ERROR_THREAD_CREATE;
}
ret = pthread_create(&monitor_thread_, NULL, receive_monitor_command, NULL);
if (ret != 0)
{
//error, destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
pthread_rwlock_destroy(&ip_list_lock);
return ERROR_THREAD_CREATE;
}
usleep(INIT_TIME);
if(created_threads != ANALYSE_THREAD_MAX)
{
//error, destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
pthread_rwlock_destroy(&ip_list_lock);
return ERROR_THREAD_CREATE;
}
pthread_join(receive_thread_, NULL);
printf("receive thread %d canceled!/n/n", receive_thread_);
for (v_int_32 k=0; k<ANALYSE_THREAD_MAX; k++)
{
if(analyze_thread_[k] != 0)
{
pthread_join(analyze_thread_[k], NULL);
printf("analyse thread %d canceled!/n/n", analyze_thread_[k]);
}
}
pthread_join(monitor_thread_, NULL);
printf("monitor thread %d canceled!/n/n", monitor_thread_);
pthread_join(signal_handle_thread_, NULL);
printf("signal thread %d canceled!/n/n", signal_handle_thread_);
return NO_ERROR;
}
上面这个是主函数,它创建了1个receive_thread_, MAX个analyse_thread_, 1个signal_handle_thread_和1个monitor_thread_,其中monitor_thread_在接受到一个连接的时候还会创建一个send_to_monitor_thread_。
v_int_32 start()
{
v_int_32 ret = 0;
v_int_32 created_threads = 0;
sigset_t bset, oset;
sigemptyset(&bset);
sigaddset(&bset, SIGINT);//建立一个信号集,将SIGINT添加进去
if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0) {//设置父线程的信号掩码,子线程会继承这信号掩码
printf("set thread signal mask failrue!/n");
}
ret = pthread_create(&receive_thread_, NULL, receive_packet, (void*)analysis_queue_);
if (ret != 0)
{
//error, destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
pthread_rwlock_destroy(&ip_list_lock);
return ERROR_THREAD_CREATE;
}
usleep(INIT_TIME);
for (v_int_32 j=0; j<ANALYSE_THREAD_MAX; j++)
{
ret = pthread_create(&analyze_thread_[j], NULL, analysis_packet, (void*)analysis_queue_[j]);
if (ret != 0)
{
break;
}
created_threads++;
usleep(INIT_TIME); //To ensure the binding to cpu is finished
}
ret = pthread_create(&signal_handle_thread_, NULL, handle_signal, NULL);
if (ret != 0)
{
//error, destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
pthread_rwlock_destroy(&ip_list_lock);
return ERROR_THREAD_CREATE;
}
ret = pthread_create(&monitor_thread_, NULL, receive_monitor_command, NULL);
if (ret != 0)
{
//error, destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
pthread_rwlock_destroy(&ip_list_lock);
return ERROR_THREAD_CREATE;
}
usleep(INIT_TIME);
if(created_threads != ANALYSE_THREAD_MAX)
{
//error, destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
pthread_rwlock_destroy(&ip_list_lock);
return ERROR_THREAD_CREATE;
}
pthread_join(receive_thread_, NULL);
printf("receive thread %d canceled!/n/n", receive_thread_);
for (v_int_32 k=0; k<ANALYSE_THREAD_MAX; k++)
{
if(analyze_thread_[k] != 0)
{
pthread_join(analyze_thread_[k], NULL);
printf("analyse thread %d canceled!/n/n", analyze_thread_[k]);
}
}
pthread_join(monitor_thread_, NULL);
printf("monitor thread %d canceled!/n/n", monitor_thread_);
pthread_join(signal_handle_thread_, NULL);
printf("signal thread %d canceled!/n/n", signal_handle_thread_);
return NO_ERROR;
}
上面这个是主函数,它创建了1个receive_thread_, MAX个analyse_thread_, 1个signal_handle_thread_和1个monitor_thread_,其中monitor_thread_在接受到一个连接的时候还会创建一个send_to_monitor_thread_。
之所以要在父线程创建子线程之前,将SIGINT信号加入阻塞信号集,是因为子线程会继承父线程的信号掩码,即对于子线程来说,SIGINT信号也在自己的阻塞信号集之中。当阻塞信号产生的时候,如果线程对该信号的动作是默认动作或者捕获信号,那么这个阻塞信号将会被挂起,知道线程解开信号的阻塞,或者改变动作为忽略。
对于我们这个例子来说,当所有的线程接受到SIGINT信号的时候,除了signal_handle_thread_线程之外的所有线程将继续执行。signal_thread等到SIGINT信号的时候,将会取消掉其他几个子线程,并且回收资源。signal_thread_是用来捕获SIGINT信号的线程,它的工作代码如下:
void* DpiPacketProcesser::handle_signal(void*)
{
sigset_t waitset, oset;
int sig;
sigemptyset(&waitset);
sigaddset(&waitset, SIGINT); //将SIGINT信号加入等待的信号集当中
sigwait(&waitset, &sig); //在此阻塞,直到SIGINT信号到达
if (send_to_monitor_thread_ != 0) {//如果有这个线程,则终止它
//cancel send thread
printf("/ncancel send thread!/n");
pthread_cancel(send_to_monitor_thread_);
printf("wait for send thread!/n");
pthread_join(send_to_monitor_thread_, NULL);
printf("send thread %d canceled!/n/n", send_to_monitor_thread_);
}
//cancel receive thread
printf("cancel receive thread!/n");
pthread_cancel(receive_thread_);//取消reveive_thread_
printf("wait for receive thread!/n/n");
//cancel analyse thread
for (v_int_32 k=0; k<ANALYSE_THREAD_MAX; k++)
{
if(analyze_thread_[k] != 0)
{
printf("cancel the analyse thread!/n");
pthread_cancel(analyze_thread_[k]);//取消analyse_thread_
printf("wait for analyse thread!/n/n");
}
}
//cancel monitor thread
printf("cancel monitor thread!/n");
pthread_cancel(monitor_thread_);//取消monitor_thread_
printf("wait for monitor thread!/n/n");
//close listening socket
close(listen_sock);//关闭监听套接口
printf("listen socket closed!/n");
//destroy tc list lock
pthread_rwlock_destroy(&tc_list_lock);
printf("tc_list_lock destroyed!/n");
pthread_rwlock_destroy(&tc_list_lock);
printf("tc_list_lock destroyed!/n");
//destroy ip list lock
pthread_rwlock_destroy(&ip_list_lock);
printf("tc_list_lock destroyed!/n/n");
return NULL;
}
pthread_rwlock_destroy(&ip_list_lock);
printf("tc_list_lock destroyed!/n/n");
return NULL;
}
############################################
linux多线程环境的信号处理
传统的信号处理方式是建立某个特定信号的信号处理程序,比如对SIGALRM的处理,可以通过signal(SIGALRM,alarm_handler)进行处理。其中alarm_handler的原型为:void func(void *);
但是需要注意的是这种信号处理方式是异步的,不知道什么时候会发生,而且在信号处理函数里一般不建议执行那些耗时比较久的程序,另外,处理函数也不是可重入的,为安全起见,在函数里面只能调用少数一些可重入(reentrant)的函数。
为什么呢?首先,在信号处理中有可能又引起新的中断,如果处理函数中调用了某个需要同步的函数,而在调用过程中又发生了新的中断,新的中断处理或许也要取得资源锁,那么很容易造成死锁。总而言之,处理函数可能是多线程安全的,但不是可重入的。
在多线程环境下,产生的信号是传递给整个进程的,一般而言,所有线程都有机会收到这个信号,进程在收到信号的的线程上下文执行信号处理函数,具体是哪个线程执行的难以获知。
为了解决这种问题,可以使用同步处理异步信号的方法,由主线程派生一个新线程用于信号管理。posix标准中的sigwait方法用于监听阻塞信号集合,如果发现其中的某些信号达到,就可以调用自定义的信号处理方法。
例如下面的程序:
void sig_handler(int signum);
void *sig_handler_thread(void *arg);
void *worker_thread(void *arg);
int main(int argc,char **argv)
{
//注意这里也定义了一个传统的信号处理函数,但实际上它并没有被调用到
signal(SIGALRM,alarmHandler);
pid_t pid=getpid();
pthread_t ppid;
//阻塞信号集合定义,bset为阻塞信号集,oset为旧的信号集
sigset_t bset,oset;
sigemptyset(&bset);
//阻塞sigalrm信号
sigaddset(&bset,SIGALRM);
//阻塞sigint信号,不理会它的默认行为(退出),而是使用自己的信号处理函数
sigaddset(&bset,SIGINT);
//设置信号集合掩码
//pthread_sigmask第一个参数是how,指明了处理方式是阻塞。
if(pthread_sigmask(SIG_BLOCK,&bset,&oset)!=0){
printf("pthread_sigmask error!\n");
}
//启动信号处理线程
pthread_create(&ppid,NULL,sig_handler_thread,NULL);
alarm(1);
//启动工作线程
pthread_t worker;
pthread_create(&worker,NULL,worker_thread,NULL);
while(1){
sleep(1);
}
return 0;
}
void sig_handler(int signum){
printf("receive signal:%d\n",signum);
sleep(1);
// alarm(1);
pid_t pid=getpid();
kill(pid,SIGALRM);
}
void* sig_handler_thread(void *arg){
//信号处理线程中需要指定关注的信号,并将它们添加到waitset中
//这样一来,当出现感兴趣的信号时,就会调用sig_handler函数。
//这里只关注SIGALRM和SIGINT两个函数。
//注意在主线程中已经指定了阻塞信号掩码集合,而sig_handler_thread是主线程生成的,所以
//它也继承了同样的阻塞信号掩码集合,如果在下面注释了sigaddset(&waitset,SIGINT),则在shell
中Ctrl+C时它将不调用sig_handler()函数。
sigset_t waitset,oset;
pthread_detach(pthread_self());
int rv,sig;
sigemptyset(&waitset);
sigaddset(&waitset,SIGALRM);
sigaddset(&waitset,SIGINT);
while(1){
rv=sigwait(&waitset,&sig);
if(rv!=-1){
sig_handler(sig);
}else{
printf("sigwait return error.%s\n",strerror(errno));
}
}
}
void *worker_thread(void *arg){
pthread_detach(pthread_self());
printf("i am worker.\n");
pthread_exit(NULL);
}
void sig_handler(int signum);
void *sig_handler_thread(void *arg);
void *worker_thread(void *arg);
int main(int argc,char **argv)
{
//注意这里也定义了一个传统的信号处理函数,但实际上它并没有被调用到
signal(SIGALRM,alarmHandler);
pid_t pid=getpid();
pthread_t ppid;
//阻塞信号集合定义,bset为阻塞信号集,oset为旧的信号集
sigset_t bset,oset;
sigemptyset(&bset);
//阻塞sigalrm信号
sigaddset(&bset,SIGALRM);
//阻塞sigint信号,不理会它的默认行为(退出),而是使用自己的信号处理函数
sigaddset(&bset,SIGINT);
//设置信号集合掩码
//pthread_sigmask第一个参数是how,指明了处理方式是阻塞。
if(pthread_sigmask(SIG_BLOCK,&bset,&oset)!=0){
printf("pthread_sigmask error!\n");
}
//启动信号处理线程
pthread_create(&ppid,NULL,sig_handler_thread,NULL);
alarm(1);
//启动工作线程
pthread_t worker;
pthread_create(&worker,NULL,worker_thread,NULL);
while(1){
sleep(1);
}
return 0;
}
void sig_handler(int signum){
printf("receive signal:%d\n",signum);
sleep(1);
// alarm(1);
pid_t pid=getpid();
kill(pid,SIGALRM);
}
void* sig_handler_thread(void *arg){
//信号处理线程中需要指定关注的信号,并将它们添加到waitset中
//这样一来,当出现感兴趣的信号时,就会调用sig_handler函数。
//这里只关注SIGALRM和SIGINT两个函数。
//注意在主线程中已经指定了阻塞信号掩码集合,而sig_handler_thread是主线程生成的,所以
//它也继承了同样的阻塞信号掩码集合,如果在下面注释了sigaddset(&waitset,SIGINT),则在shell
中Ctrl+C时它将不调用sig_handler()函数。
sigset_t waitset,oset;
pthread_detach(pthread_self());
int rv,sig;
sigemptyset(&waitset);
sigaddset(&waitset,SIGALRM);
sigaddset(&waitset,SIGINT);
while(1){
rv=sigwait(&waitset,&sig);
if(rv!=-1){
sig_handler(sig);
}else{
printf("sigwait return error.%s\n",strerror(errno));
}
}
}
void *worker_thread(void *arg){
pthread_detach(pthread_self());
printf("i am worker.\n");
pthread_exit(NULL);
}
更多推荐
已为社区贡献6条内容
所有评论(0)