Linux下多線程編程與信號處理易疏忽的一個例子

這幾天把一個網絡流量採集器程序基本改好了,原來在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信號,表示子進程退出,由於該信號沒有被阻塞,因此會導致主進程(線程)也立刻退出,出現了前述的運行情況。因而該問題的一個解決方法是在子線程生成前進行信號設置, 或在子線程內部進行信號設置。 由於子線程是往往是一個事務處理函數,因此我建議在簡單的情況下采用前者,如果需要處理的信號比較複雜,那就必須使用後一種方法來處理。這樣,以上的程序邏輯改爲如下就可以了:

#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;

}

 

原文地址:http://www.fixdown.com/wz/article/23/25/2006/57421.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章