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