linux多線程中信號的使用(1)

在Linux的多線程中使用信號機制,與在進程中使用信號機制有着根本的區別,可以說是完全不同。在進程環境中,對信號的處理是,先註冊信號處理函數,當信號異步發生時,調用處理函數來處理信號。它完全是異步的(我們完全不知到信號會在進程的那個執行點到來!)。然而信號處理函數的實現,有着許多的限制;比如有一些函數不能在信號處理函數中調用;再比如一些函數read、recv等調用時會被異步的信號給中斷(interrupt),因此我們必須對在這些函數在調用時因爲信號而中斷的情況進行處理(判斷函數返回時 enno 是否等於 EINTR)。


但是在多線程中處理信號的原則卻完全不同,它的基本原則是:將對信號的異步處理,轉換成同步處理,也就是說用一個線程專門的來“同步等待”信號的到來,而其它的線程可以完全不被該信號中斷/打斷(interrupt)。這樣就在相當程度上簡化了在多線程環境中對信號的處理。而且可以保證其它的線程不受信號的影響。這樣我們對信號就可以完全預測,因爲它不再是異步的,而是同步的我們完全知道信號會在哪個線程中的哪個執行點到來而被處理!。而同步的編程模式總是比異步的編程模式簡單。其實多線程相比於多進程的其中一個優點就是:多線程可以將進程中異步的東西轉換成同步的來處理。

1. sigwait函數:
  1. sigwait - wait for a signal

  2. #include <signal.h>
  3. int sigwait(const sigset_t *set, int *sig);

  4. Description
  5. The sigwait() function suspends execution of the calling thread until the delivery of one 
  6. of the signals specified in the signal set set. The function accepts the signal (removes 
  7. it from the pending list of signals), and returns the signal number in sig.

  8. The operation of sigwait() is the same as sigwaitinfo(2), except that:

  9. * sigwait() only returns the signal number, rather than a siginfo_t structure describing 
  10.   the signal.
  11. * The return values of the two functions are different.

  12. Return Value

  13. On success, sigwait() returns 0. On error, it returns a positive error number.
從上面的man sigwait的描述中,我們知道:sigwait是同步的等待信號的到來,而不是像進程中那樣是異步的等待信號的到來。sigwait函數使用一個信號集作爲他的參數,並且在集合中的任一個信號發生時返回該信號值,解除阻塞,然後可以針對該信號進行一些相應的處理。
sigwait原理:監聽信號集set中所包含的信號,並將其存在signo中。注意:sigwait函數所監聽的信號在之前必須被阻塞。sigwait函數將阻塞調用他的線程,直到收到它所監聽的信號發生了,然後sigwait將其從未決隊列中取出(因爲被阻塞了,所以肯定是未決了),但是有一點需要注意的是:它從未決隊列取出之後,並不影響那個被取出的信號原來被阻塞的狀態。它所做的工作只有兩個:第一,監聽被阻塞的信號;第二,如果所監聽的信號產生了,則將其從未決隊列中移出來(這裏實時信號和非實時信號又有區別,體現在取出的順序等,具體自己取網上查,這裏不再詳述)。在一些帖子中看到:sigwait取出未決信號之後,並將其原來的阻塞狀態轉爲非阻塞狀態,這是嚴重錯誤的,sigwait並不改變信號的阻塞與非阻塞狀態,它只做上面的兩個工作。(以上描述有錯的話,歡迎指正)
2. 記住:
     在多線程代碼中,總是使用sigwait或者sigwaitinfo或者sigtimedwait等函數來處理信號。
     而不是signal或者sigaction等函數。因爲在一個線程中調用signal或者sigaction等函數會改變所以線程中的
     信號處理函數。而不是僅僅改變調用signal/sigaction的那個線程的信號處理函數。
3. pthread_sigmask函數:
   每個線程均有自己的信號屏蔽集(信號掩碼),可以使用pthread_sigmask函數來屏蔽某個線程對某些信號的
   響應處理,僅留下需要處理該信號的線程來處理指定的信號。實現方式是:利用線程信號屏蔽集的繼承關係
  (在主進程中對sigmask進行設置後,主進程創建出來的線程將繼承主進程的掩碼, 不要在線程的信號掩碼中阻塞不能被忽略處理的兩個信號 SIGSTOP 和 SIGKILL。
  1. pthread_sigmask - examine and change mask of blocked signals

  2. #include <signal.h>
  3. int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

  4. Compile and link with -pthread.

  5. DESCRIPTION
  6. The pthread_sigmask() function is just like sigprocmask(2), with the difference that its use
  7. in multithreaded programs is explicitly specified by POSIX.1-2001.
  8. Other differences are noted in this page.
  9. For a description of the arguments and operation of this function, see sigprocmask(2).

  10. RETURN VALUE
  11. On success, pthread_sigmask() returns 0; on error, it returns an error number.
  12. NOTES
  13. A new thread inherits a copy of its creator's signal mask.

  14. (from man sigprocmask: )

  15. The behavior of the call is dependent on the value of how, as follows.
  16. SIG_BLOCK
  17. The set of blocked signals is the union of the current set and the set argument.
  18. SIG_UNBLOCK
  19. The signals in set are removed from the current set of blocked signals. It is permissible 
  20. to attempt to unblock a signal which is not blocked.
  21. SIG_SETMASK
  22. The set of blocked signals is set to the argument set.

  23. If oldset is non-NULL, the previous value of the signal mask is stored in oldset.

  24. If set is NULL, then the signal mask is unchanged (i.e., how is ignored), but the current 
  25. value of the signal mask is nevertheless returned in oldset (if it is not NULL).
4. pthread_kill函數:
   在多線程程序中,一個線程可以使用pthread_kill對同一個進程中指定的線程(包括自己)發送信號。注意在多線程中  
  一般不使用kill函數發送信號,因爲kill是對進程發送信號,結果是:正在運行的線程會處理該信號,如果該線程沒有
 註冊信號處理函數,那麼會導致整個進程退出。
  1. #include <signal.h>
  2. int pthread_kill(pthread_t thread, int sig);

  3. Compile and link with -pthread.

  4. DESCRIPTION
  5. The pthread_kill() function sends the signal sig to thread, another thread in the same 
  6. process as the caller. The signal is asynchronously directed to thread.

  7. If sig is 0, then no signal is sent, but error checking is still performed; this can be 
  8. used to check for the existence of a thread ID.

  9. RETURN VALUE
  10. On success, pthread_kill() returns 0; on error, it returns an error number, and no signal 
  11. is sent.

  12. ERRORS
  13. ESRCH No thread with the ID thread could be found.
  14. EINVAL An invalid signal was specified.
5. 記住:調用sigwait同步等待的信號必須在調用線程中被屏蔽,並且通常應該在所有的線程中被屏蔽(這樣可以保證信號絕不會被送到除了調用sigwait的任何其它線程),這是通過利用信號掩碼的繼承關係來達到的。
(The semantics of sigwait require that all threads (including the thread calling sigwait) have the signal masked, for
  reliable operation. Otherwise, a signal that arrives not blocked in sigwait might be  delivered to another thread.)
6. 代碼示例:(from man pthread_sigmask)
  1. #include <pthread.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <errno.h>

  7. /* Simple error handling functions */

  8. #define handle_error_en(en, msg) \
  9.         do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

  10. static void * 
    1. sig_thread(void *arg)
  11. {
  12.       sigset_t *set = (sigset_t *) arg;
  13.       int s, sig;

  14.       for (;;) {
  15.             s = sigwait(set, &sig);
  16.             if (!= 0)
  17.                   handle_error_en(s, "sigwait");
  18.             printf("Signal handling thread got signal %d\n", sig);
  19.       }
  20. }

  21. int 
    1. main(int argc, char *argv[])
  22. {
  23.       pthread_t thread;
  24.       sigset_t set;
  25.       int s;

  26.       /* 
  27.          Block SIGINT; other threads created by main() will inherit
  28.          a copy of the signal mask. 
  29.        */
  30.       sigemptyset(&set);
  31.       sigaddset(&set, SIGQUIT);
  32.       sigaddset(&set, SIGUSR1);
  33.       s = pthread_sigmask(SIG_BLOCK, &set, NULL);
  34.       if (!= 0)
  35.             handle_error_en(s, "pthread_sigmask");
  36.       s = pthread_create(&thread, NULL, &sig_thread, (void *) &set);
  37.       if (!= 0)
  38.             handle_error_en(s, "pthread_create");
  39.       /* 
  40.         Main thread carries on to create other threads and/or do
  41.         other work 
  42.        */
  43.       pause(); /* Dummy pause so we can test program */
  44.       return 0;
  45. }
編譯運行情況:
  1. digdeep@ubuntu:~/pthread/learnthread$ gcc -Wall -pthread -o pthread_sigmask pthread_sigmask.
  2. digdeep@ubuntu:~/pthread/learnthread$ ./pthread_sigmask &
  3. [1] 4170
  4. digdeep@ubuntu:~/pthread/learnthread$ kill -QUIT %1
  5. digdeep@ubuntu:~/pthread/learnthread$ Signal handling thread got signal 3

  6. digdeep@ubuntu:~/pthread/learnthread$ kill -USR1 %1
  7. digdeep@ubuntu:~/pthread/learnthread$ Signal handling thread got signal 10

  8. digdeep@ubuntu:~/pthread/learnthread$ kill -TERM %1
  9. digdeep@ubuntu:~/pthread/learnthread$ 
  10. [1]+ Terminated ./pthread_sigmask
  11. digdeep@ubuntu:~/pthread/learnthread$
這個例子演示了:通過在主線程中阻塞一些信號,其它的線程會繼承信號掩碼,然後專門用一個線程使用sigwait函數來同步的處理信號,使其它的線程不受到信號的影響。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章