您的位置:首页 > 运维架构 > Linux

Linux 多线程编程 与 信号处理

2014-02-24 11:40 337 查看
     原来在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>      //信号处理所需要的头文件
int main(int argc, char * argv[]){

  //其他所需要的变量声明  

  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;

}

#################################

多线程下安全的信号处理

     最近项目上出现了一个小问题,今天把这个问题修补了一下。为此在博客上也做个记录。

     问题是有关端口绑定引起的,原来我们在做测试的时候,一般用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_。

     之所以要在父线程创建子线程之前,将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");
       
     //destroy ip list lock
     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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Linux 多线程