多線程中的信號處理


  在linux下寫服務器,處理信號在所難免。在多線程和單線程中信號的處理還是有點不同的。參考:

http://maxim.int.ru/bookshelf/PthreadsProgram/htm/r_40.html

http://aboocool.blog.51cto.com/3189391/626675

  在linux下,每個進程都有自己的signal mask,這個信號掩碼指定哪個信號被阻塞,哪個不會被阻塞,通常用調用sigmask來處理。同時每個進程還有自己的signal action,這個行為集合指定了信號該如何處理,通常調用sigaction來處理。

  使用了多線程后,便有些疑問:

  1. 信號發生時,哪個線程會收到
  2. 是不是每個線程都有自己的mask及action
  3. 每個線程能按自己的方式處理信號么

  首先,信號的傳遞是根據情況而定的:

  • 如果是異常產生的信號(比如程序錯誤,像SIGPIPE、SIGEGV這些),則只有產生異常的線程收到並處理。
  • 如果是用pthread_kill產生的內部信號,則只有pthread_kill參數中指定的目標線程收到並處理。
  • 如果是外部使用kill命令產生的信號,通常是SIGINT、SIGHUP等job control信號,則會遍歷所有線程,直到找到一個不阻塞該信號的線程,然后調用它來處理。(一般從主線程找起),注意只有一個線程能收到。

  其次,每個線程都有自己獨立的signal mask,但所有線程共享進程的signal action。這意味着,你可以在線程中調用pthread_sigmask(不是sigmask)來決定本線程阻塞哪些信號。但你不能調用sigaction來指定單個線程的信號處理方式。如果在某個線程中調用了sigaction處理某個信號,那么這個進程中的未阻塞這個信號的線程在收到這個信號都會按同一種方式處理這個信號。另外,注意子線程的mask是會從主線程繼承而來的。

  第三個問題,因為signal action共享的問題,已經知道不能。

  下面以一個例子說明:

/*threadsig.c*/
#include <signal.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void sighandler(int signo);
 
void *
thr1_fn(void *arg)
{
    struct sigaction    action;
    action.sa_flags = 0;
    action.sa_handler = sighandler;
       
    sigaction(SIGINT, &action, NULL);
    
    pthread_t   tid = pthread_self();
    int     rc;
 
    printf("thread 1 with tid:%lu\n", tid);
    rc = sleep(60);
    if (rc != 0)
        printf("thread 1... interrupted at %d second\n", 60 - rc);
    printf("thread 1 ends\n");
    return NULL;
}
 
void *
thr2_fn(void *arg)
{
    struct sigaction    action;
    pthread_t       tid = pthread_self();
    int         rc, err;
   
    printf("thread 2 with tid:%lu\n", tid);
     
    action.sa_flags = 0;
    action.sa_handler = sighandler;
       
    err = sigaction(SIGALRM, &action, NULL);
     
    rc = sleep(60);
    if (rc != 0)
        printf("thread 2... interrupted at %d second\n", 60 - rc);
    printf("thread 2 ends\n");
    return NULL;
}
 
void *
thr3_fn(void *arg)
{
    pthread_t   tid = pthread_self();
    sigset_t    mask;
    int     rc, err;
   
    printf("thread 3 with tid%lu\n", tid);
 
     
    sigemptyset(&mask); /* 初始化mask信號集 */
   
    sigaddset(&mask, SIGALRM);
    err = pthread_sigmask(SIG_BLOCK, &mask, NULL);
    if (err != 0)
    {
        printf("%d, %s/n", rc, strerror(rc));
        return NULL;
    }
   
    rc = sleep(10);
    if (rc != 0)
        printf("thread 3... interrupted at %d second\n", 60 - rc);
    err = pthread_sigmask( SIG_UNBLOCK,&mask,NULL );
    if ( err != 0 )
    {
        printf("unblock %d, %s/n", rc, strerror(rc));
        return NULL;
    }
    
    rc = sleep(10);
    if (rc != 0)
        printf("thread 3... interrupted at %d second after unblock\n", 60 - rc);

    printf("thread 3 ends\n");
    return NULL;
 
    return NULL;
}
 
int
main(void)
{
    int     rc, err;
    pthread_t   thr1, thr2, thr3, thrm = pthread_self();
 
    printf("thread main with pid %lu\n",thrm);
    err = pthread_create(&thr1, NULL, thr1_fn, NULL);
    if (err != 0) {
        printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
        exit(1);
    }
 
     
/*  pthread_kill(thr1, SIGALRM);    send a SIGARLM signal to thr1 before thr2 set the signal handler, then the whole process will be terminated*/
    err = pthread_create(&thr2, NULL, thr2_fn, NULL);
    if (err != 0) {
        printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
        exit(1);
    }
     
    err = pthread_create(&thr3, NULL, thr3_fn, NULL);
    if (err != 0) {
        printf("error in creating pthread:%d\t%s\n",err, strerror(rc));
        exit(1);
    }
 
    sleep(10);
    //內部產生的信號,只有指定的線程能收到,因此要向所有線程發送
    pthread_kill(thr1, SIGALRM);
    pthread_kill(thr2, SIGALRM);
    pthread_kill(thr3, SIGALRM);
    pthread_kill(thr3, SIGALRM);
    pthread_kill(thr3, SIGALRM);
    sleep(5);
    pthread_join(thr1, NULL);   /*wait for the threads to complete.*/
    pthread_join(thr2, NULL);
    pthread_join(thr3, NULL);
    printf("main ends\n");
    return 0;
}
 
void
sighandler(int signo)
{
    pthread_t   tid = pthread_self();
     
    printf("thread with pid:%lu receive signo:%d\n", tid, signo);
    return;
}

在上面的代碼中,主線程創建三個線程。線程1注冊SIGINT信號(即ctrl+c) ,線程2注冊SIGALRM,線程三則是先阻塞SIGALRM,然后解除阻塞。

編譯后看運行結果:

xzc@xzc-HP-ProBook-4446s:~/code/test$ gcc -o threadsig threadsig.c -pthread
xzc@xzc-HP-ProBook-4446s:~/code/test$ ./threadsig 
thread main with pid 139946922108736
thread 2 with tid:139946905396992
thread 1 with tid:139946913789696
thread 3 with tid139946897004288
^Cthread with pid:139946922108736 receive signo:2
thread with pid:139946913789696 receive signo:14
thread 1... interrupted at 4 second
thread 1 ends
thread with pid:139946905396992 receive signo:14
thread 2... interrupted at 4 second
thread 2 ends
^Cthread with pid:139946922108736 receive signo:2
^Cthread with pid:139946922108736 receive signo:2
thread with pid:139946897004288 receive signo:14
thread 3 ends
main ends
xzc@xzc-HP-ProBook-4446s:~/code/test$ 

在第一行紅色的地方,主線程正在sleep,我按下ctrl+c,只有主線程收到並處理了信號。說明進程會從主線程開始查找不阻塞該信號的線程來處理job control類的信號。

由於主線程sleep被打斷,隨后向三個線程發送了SIGALRM,線程1、2由於沒有阻塞該信號,被迫從sleep中醒來,並結束進程。進程3仍在sleep。

在第二行紅色的地方,線程3第一次sleep終於完成,解除了對SIGALRM的阻塞。於是馬上收到被阻塞的SIGALRM(發送3次,只收到一次)。PS:請注意信號阻塞與忽略的區別。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM