這幾天把一個網絡流量採集器程序基本改好了,原來在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;
}
|