每個線程都有自己的信號屏蔽字,但是信號的處理是進程中所有線程共享的。這意味著盡管單個線程可以阻止某些信號,但當線程修改了與某個信號相關的處理行為以后,所有的線程都必須共享這個處理行為的改變。
進程中的信號是遞送到單個線程的。如果信號與硬件故障或計時器超時相關,該信號就被發送到引起該事件的線程中去,而其他的信號則被發送到任意一個線程。
http://www.CUOXin.com/nufangrensheng/p/3515257.html中討論了進程如何使用sigPRocmask來阻止信號發送。sigpromask的行為在多線程的進程中并沒有定義,線程必須使用pthread_sigmask。
#include <signal.h>int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);返回值:若成功則返回0,否則返回錯誤編號
pthread_sigmask函數與sigprocmask函數基本相同,除了pthread_sigmask工作在線程中,并且失敗時返回錯誤碼,而不像sigprocmask中那樣設置errno并返回-1。
線程可以通過調用sigwait等待一個或多個信號發生。
#include <signal.h>int sigwait(const sigset_t *restrict set, int *restrict signop);返回值:若成功則返回0,否則返回錯誤編號
set參數指出了線程等待的信號集,signop指向的整數將作為返回值,表明信號編號。
如果信號集中的某個信號在sigwait調用的時候處于未決狀態,那么sigwait將無阻塞地返回,在返回之前,sigwait將從進程中移除那些處于未決狀態的信號。為了避免錯誤動作發生,線程在調用sigwait之前,必須阻塞那些它正在等待的信號。sigwait函數會自動取消信號集的阻塞狀態,直到有新的信號被遞送。在返回之前,sigwait將恢復線程的信號屏蔽字。如果信號在sigwait調用的時候沒有被阻塞,在完成對sigwait調用之前會出現一個時間窗,在這個時間窗口期,某個信號可能在完成sigwait調用之前就被遞送了。
使用sigwait的好處在于它可以簡化信號處理,允許把異步產生的信號用同步的方式處理。為了防止信號中斷線程,可以把信號加到每個線程的信號屏蔽字中,然后安排專用線程作信號處理。這些專用線程可以進行函數調用,不需要擔心在信號處理程序中調用哪些函數是安全的,因為這些函數調用來自正常的線程環境,而非傳統的信號處理程序,傳統信號處理程序通常會中斷線程的正常執行。
如果多個線程在sigwait調用時,等待的是同一個信號,這時就會出現線程阻塞。當信號遞送的時候,只有一個線程可以從sigwait中返回。如果信號被捕獲(例如進程通過使用sigaction建立了一個信號處理函數),而且線程正在sigwait調用中等待同一個信號,那么這時將由操作系統實現來決定以何種方式遞送信號。在這種情況下,操作系統實現可以讓sigwait返回,也可以激活信號處理程序,但不可能出現兩者皆可的情況。
要把信號發送到進程,可以調用kill(見http://www.CUOXin.com/nufangrensheng/p/3514817.html);要把信號發送到線程,可以調用pthread_kill。
#include <signal.h>int pthread_kill(pthread_t thread, int signo);返回值:若成功則返回0,否則返回錯誤編號
可以傳一個0值的signo來檢查線程是否存在。如果信號的默認處理動作是終止該進程,那么把信號傳遞給某個線程仍然會殺掉整個進程。
注意鬧鐘定時器是進程資源,并且所有的線程共享相同的alarm。所以進程中的多個線程不可能互不干擾(或互不合作)地使用鬧鐘定時器。
實例
回憶程序清單10-16(http://www.CUOXin.com/nufangrensheng/p/3516427.html),等待信號處理程序設置標志,從而表明主程序應該退出。唯一可運行的控制線程就是主線程和信號處理程序,所以阻塞信號足以避免錯失標志修改。在線程中,需要使用互斥量來保護標志,如程序清單12-6所示。
程序清單12-6 同步信號處理
#include "apue.h"#include <pthread.h>int quitflag; /* set nonzero by thread */sigset_t mask;pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t wait = PTHREAD_COND_INITIALIZER;void *thr_fn(void *arg){ int err, signo; for(; ;) { err = sigwait(&mask, &signo); if(err != 0) err_exit(err, "sigwait failed"); switch(signo) { case SIGINT: printf("/ninterrupt/n"); break; case SIGQUIT: pthread_mutex_lock(&lock); quitflag = 1; pthread_mutex_unlock(&lock); pthread_cond_signal(&wait); return(0); default: printf("unexpected signal %d/n", signo); exit(1); } }}int main(void){ int err; sigset_t oldmask; pthread_t tid; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGQUIT); if((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0) err_exit(err, "SIG_BLOCK error"); err = pthread_create(&tid, NULL, thr_fn, 0); if(err != 0) err_exit(err, "can't create thread"); pthread_mutex_lock(&lock); while(quitflag == 0) pthread_cond_wait(&wait, &lock); pthread_mutex_unlock(&lock); /* SIGQUIT has been caught and is now blocked; do whatever */ quitflag = 0; /* reset signal mask which unblocks SIGQUIT */ if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); exit(0);}
這里并不讓信號處理程序中斷主控線程,而是由專門的獨立控制線程進行信號處理。改動quitflag的值是在互斥量的保護下進行的,這樣主控線程不會在調用pthread_cond_signal時錯失喚醒調用。在主控線程中使用相同的互斥量來檢查標志的值,并且原子地釋放互斥量,等待條件的發生。
注意在主線程開始時阻塞SIGINT和SIGQUIT。當創建線程進行信號處理時,新建線程繼承了現有的信號屏蔽字。因為sigwait會解除信號的阻塞狀態,所以只有一個線程可以用于信號的接收。這使得對主線程進行編碼時不必擔心來自這些信號的中斷。
運行這個程序可以得到與程序10-16類似的輸出結果:
interrupt 鍵入中斷字符interrupt 再次鍵入中斷字符interrupt 再一次 用結束字符終止
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答