Linux下多线程中的信号处理详解教程

在linux下,每个进程都有自己的signal mask,这个信号掩码指定哪个信号被阻塞,哪个不会被阻塞,通常用调用sigmask来处理。同时每个进程还有自己的signal action,这个行为集合指定了信号该如何处理,通常调用sigaction来处理。

使用了多线程后,便有些疑问:

信号发生时,哪个线程会收到
是不是每个线程都有自己的mask及action
每个线程能按自己的方式处理信号么

首先,信号的传递是根据情况而定的:

如果是异常产生的信号(比如程序错误,像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:请注意信号阻塞与忽略的区别。

pthread & signal
 
pthread线程和信号

所有的异步信号发到整个进程的所有线程(异步信号如kill, lwp_kill, sigsend, kill等调用产生的都是,异步信号也称为中断),而且所有线程共享信号的处理行为(即sigaction的设置,对于同一信号的设置,某一线程的更改会影响到所有线程)。但每个线程可以有自己的mask来阻止信号的发送,所以可以通过线程对mask的设置来决定信号发送到哪个线程。设置mask的函数为:

#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset)

此外,线程可以通过sleep(超过指定时间或调用的进程/线程捕捉到某个信号并从信号处理程序返回时,sleep返回)或者sigwait来等待一个或多个信号发生。

#include <signal.h>
int pthread_sigwait(const sigset_t *restrict set, int *restrict signop);

给进程发送信号可以调用kill,同样给线程调用信号可以使用pthread_kill

#include <signal.h>
int pthread_kill(pthread_t thread, int signo);

可以发送一个0的signo来检查线程是否存在,如果信号的默认行为是终止进程(例如SIGARLM),那么把该信号发送给某个线程会杀掉整个进程的所有线程。

另外注意ALARM是进程资源,并且所有线程共享相同的ALARM,设置一个alarm()会发送SIGARLM信号给所有线程,所以他们不可能互补干扰的使用alarm()。
 
here comes an example:
 

   /*threadsig.c*/ 
    #include <signal.h> 
    #include <pthread.h> 
    #include <stdio.h> 
     
    void sighandler(int signo); 
     
    void * 
    thr1_fn(void *arg) 
    { 
        pthread_t   tid = pthread_self(); 
        int     rc; 
     
        printf("thread 1 with tid:%u\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:%u\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%u\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(60); 
            if (rc != 0) 
                    printf("thread 3... interrupted at %d second\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 %u\n", (unsigned int)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(3); 
        pthread_kill(thr1, SIGALRM); 
        pthread_kill(thr2, SIGALRM); 
        pthread_kill(thr3, SIGALRM); 
        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:%u receive signo:%d\n", tid, signo); 
        return; 
    } 
luffy@luffy-laptop:~/workspace/myapue$ ./threadsig 
thread main with pid 3557979936
thread 1 with tid:3549923072
thread 2 with tid:3541530368
thread 3 with tid3533137664
thread with pid:3549923072 receive signo:14
thread with pid:3541530368 receive signo:14
thread 2... interrupted at 3 second
thread 1... interrupted at 3 second
thread 1 ends
thread 2 ends    #then wait for 27 seconds and thread-3 ends
thread 3 ends
main ends

 
thr2设置的信号处理程序sighandler也应用到其他线程,thr3由于设置mask所有阻塞了SIGARLM信号。

 
Reference:
APUE

Linux线程信号

1. 概念

    按照 POSIX, 异步 (外部) 信号发送到整个进程.
    所有线程共享同一个设置, 即通过 sigaction 设置的线程处置方法.
    每个线程有自己的信号掩码, 线程库根据该掩码决定将信号发送到哪个线程.
    由于Linux 线程实现上的独特性, 外部信号始终发送到特定的线程.  

2. 例子

#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#define NUMTHREADS 3
void sighand(int signo);
void *threadfunc(void *parm)
{
    pthread_t             tid = pthread_self();
    int                   rc;
    printf("Thread %u entered/n", tid);
    rc = sleep(30); /* 若有信号中断则返回剩余秒数 */
    printf("Thread %u did not get expected results! rc=%d/n", tid, rc);
    return NULL;
}
void *threadmasked(void *parm)
{
    pthread_t             tid = pthread_self();
    sigset_t              mask;
    int                   rc;
    printf("Masked thread %lu entered/n", tid);
    sigfillset(&mask); /* 将所有信号加入mask信号集 */
    /* 向当前的信号掩码中添加mask信号集 */
    rc = pthread_sigmask(SIG_BLOCK, &mask, NULL);
    if (rc != 0)
    {
        printf("%d, %s/n", rc, strerror(rc));
        return NULL;
    }
    rc = sleep(15);
    if (rc != 0)
    {
        printf("Masked thread %lu did not get expected results! rc=%d /n", tid, rc);
        return NULL;
    }
    printf("Masked thread %lu completed masked work/n", tid);
    return NULL;
}
int main(int argc, char **argv)
{
    int                     rc;
    int                     i;
    struct sigaction        actions;
    pthread_t               threads[NUMTHREADS];
    pthread_t               maskedthreads[NUMTHREADS];
    printf("Enter Testcase - %s/n", argv[0]);
    printf("Set up the alarm handler for the process/n");
    memset(&actions, 0, sizeof(actions));
    sigemptyset(&actions.sa_mask); /* 将参数set信号集初始化并清空 */
    actions.sa_flags = 0;
    actions.sa_handler = sighand;
    /* 设置SIGALRM的处理函数 */
    rc = sigaction(SIGALRM,&actions,NULL);
    printf("Create masked and unmasked threads/n");
    for(i=0; i<NUMTHREADS; ++i)
    {
        rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
        if (rc != 0)
        {
            printf("%d, %s/n", rc, strerror(rc));
            return -1;
        }
        rc = pthread_create(&maskedthreads[i], NULL, threadmasked, NULL);
        if (rc != 0)
        {
            printf("%d, %s/n", rc, strerror(rc));
            return -1;
        }
    }
    sleep(3);
    printf("Send a signal to masked and unmasked threads/n");
     /* 向线程发送SIGALRM信号 */
    for(i=0; i<NUMTHREADS; ++i)
    {
        rc = pthread_kill(threads[i], SIGALRM);
        rc = pthread_kill(maskedthreads[i], SIGALRM);
    }
    printf("Wait for masked and unmasked threads to complete/n");
    for(i=0; i<NUMTHREADS; ++i) {
        rc = pthread_join(threads[i], NULL);
        rc = pthread_join(maskedthreads[i], NULL);
    }
    printf("Main completed/n");
    return 0;
}
void sighand(int signo)
{
    pthread_t             tid = pthread_self();
    printf("Thread %lu in signal handler/n", tid);
    return;
}

3. 打印结果

Enter Testcase - ./test
Set up the alarm handler for the process
Create masked and unmasked threads
Thread 3085065104 entered
Masked thread 3076672400 entered
Thread 3068279696 entered
Masked thread 3059886992 entered
Thread 3051494288 entered
Masked thread 3043101584 entered
Send a signal to masked and unmasked threads
Thread 3085065104 in signal handler
Thread 3085065104 did not get expected results! rc=27
Thread 3068279696 in signal handler
Thread 3068279696 did not get expected results! rc=27
Thread 3051494288 in signal handler
Thread 3051494288 did not get expected results! rc=27
Wait for masked and unmasked threads to complete
Masked thread 3076672400 completed masked work
Masked thread 3059886992 completed masked work
Masked thread 3043101584 completed masked work
Main completed
 

4. 相关函数

sigaction(查询或设置信号处理方式)

#include<signal.h>
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);

sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。

如参数结构sigaction定义如下

struct sigaction
{
   void (*sa_handler) (int);
   sigset_t sa_mask;
   int sa_flags;
   void (*sa_restorer) (void);
}

sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数,其他意义请参考signal()。
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置。
sa_restorer 此参数没有使用。
sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。

 
sigfillset(将所有信号加入此信号集)

#include<signal.h>
int sigfillset(sigset_t * set);
 
sigfillset()用来将参数set信号集初始化,然后把所有的信号加入到此信号集里。
 
sigemptyset(初始化信号集)  

#include<signal.h>
int sigemptyset(sigset_t *set);
 
sigemptyset()用来将参数set信号集初始化并清空。
 
pthread_sigmask(更改或检查调用线程的信号掩码)
 
#include <pthread.h>
#include<signal.h>
int pthread_sigmask(int how, const sigset_t *new, sigset_t *old);
 
how用来确定如何更改信号组,可以为以下值之一:

    SIG_BLOCK:向当前的信号掩码中添加new,其中new表示要阻塞的信号组。
    SIG_UNBLOCK:向当前的信号掩码中删除new,其中new表示要取消阻塞的信号组。
    SIG_SETMASK:将当前的信号掩码替换为new,其中new表示新的信号掩码。

pthread_kill(向线程发送信号)
 
#include <pthread.h>
#include<signal.h>
int pthread_kill(thread_t tid, int sig);
 
pthread_kill()将信号sig发送到由tid指定的线程。tid所指定的县城必须与调用线程在同一个进程中。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索int
, 线程
进程
linux多线程信号量、linux 多线程 信号、linux信号量详解、java多线程详解、多线程详解,以便于您获取更多的相关知识。

时间: 2024-07-29 00:26:05

Linux下多线程中的信号处理详解教程的相关文章

Linux下shell中case命令详解

  linux下shell中的case名字和C/C++中的switch类似,但是shell中的case更强大和复杂. 1.强大主要体现在:shell中case中的关键字可以是字符串类型,而每一项中可以包含正则表达式. 2.复杂主要体现在:shell中case的每一个项后有三个选择:break(常规的break).unconditional follow up(无条件继续)和conditional follow up(有条件继续). 这篇文章的重点是上面的第2点. case的无条件继续和有条件继续

Linux下Android ADB驱动安装详解

Linux下Android ADB驱动安装详解 概述 最近由于内置的合作商比较多,本人使用的Ubuntu系统好多厂商的Android手机都无法正确的识别,经过一番折腾,和查阅SDK,现把Linux下ADB驱动配置的方法和当中会遇到的相关问题的解决方法整理出来贡献给大家. Linux下使用手机USB调试模式连接ADB进行Android程序的调试,配置驱动没有Windows来的直观. 具体步骤 首先确认手机连接上电脑,lsusb查看下设备记录. matthew@matthew-1230-laptop

Linux下的压缩解压缩命令详解

linux zip命令 zip -r myfile.zip ./*将当前目录下的所有文件和文件夹全部压缩成myfile.zip文件,-r表示递归压缩子目录下所有文件. 2.unzipunzip -o -d /home/sunny myfile.zip把myfile.zip文件解压到 /home/sunny/-o:不提示的情况下覆盖文件:-d:-d /home/sunny 指明将文件解压缩到/home/sunny目录下: 3.其他zip -d myfile.zip smart.txt删除压缩文件中

linux下配置yum源方法详解_Linux

本人使用的方法一,成功配置,方法二没测过,可以作为参考 方法一: 1.确保RHEL5中已经安装了yum [root@lvs-master ~]# rpm -qa |grep yum yum-metadata-parser-2-el5 yum-updatesd-9-el5 yum-22-el5 yum-security-16-el5 yum-rhn-plugin-4-el5 2.修改源配置文件 #vim /etc/yum.repos.d/CentOS-Base.repo (如果目录下没有.repo

Linux下视频流媒体直播服务器搭建详解

目标: 搭建网络直播流媒体服务器系统(Linux操作系统)   背景: 用于OTT-TV大并发的直播和点播的一套流媒体服务器系统.支持N x 24小时录制回看和直播的服务器端解决方案.   解决方案: l  微软的Windows Media Services l  服务端软件:Windows Media Server l  平台:Windows l  文件格式:ASF, WMV -----------------------------------------------------------

Linux下nl命令的用法详解

Linux中nl命令和cat命令很像,不过nl命令会打上行号,属于比较不常用的命令,下面随小编一起来了解下这个鲜为人知的nl命令吧. nl命令在linux系统中用来计算文件中行号.nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等等的功能. 1.命令格式: nl [选项]... [文件]... 2.命令参数: -b :指定行号指定的方式,主要有两种: -b a :表示不论是否为空行,也同样列出

Linux下C编程:getch()详解

getch() 所在头文件:conio.h 函数用途:从控制台读取一个字符 函数原型:int getch(void) 返回值:读取的字符 例如: char ch;或int ch: getch();或ch=getch(); 用getch();会等待你按下任意键,再继续执行下面的语句: 用ch=getch();会等待你按下任意键之后,把该键字符所对应的ASCII码赋给ch,再执行下面的语句. 易错点: 1.所在头文件是conio.h.而不是stdio.h. 2.在使用之前要调用initscr(),结

Linux下partprobe命令的使用详解

  linux上,在安装系统之后,可否创建分区并且在不重新启动机器的情况下系统能够识别这些分区? 解决方法: 你可以使用一个叫做partprobe的工具.它包含在parted的rpm软件包中.在Red Hat Enterprise Linux 3上他的版本是parted-1.6. partprobe 是一个可以修改kernel中分区表的工具.可以使kernel重新读取分区表.如下命令可以查看你的系统是否安装了parted软件包 代码如下: rpm -q parted 举例来说: 代码如下: #

Linux下C调用C++接口详解

    C++做久了,经常用C++的方式去思考问题,有时候就突然发现自己不太会写C程序了.写程序的时候,难免会用到第三方插件或者是库,而这些插件或者库很多时候都不能完全满足我们的需求,遇到这种情况,如果全是C++,那好办,写个适配器就OK了,关于适配器模式参考我的博客<C++ Adaptor 设计模式>    如果要提供给C程序使用,那就需要自己封装C程序可以使用的库.前几天在CSDN Linux/Unix版闲逛的时候,遇到一位网友,他问这样的问题:(原话我记不住了,大致是这个意思)我要要封装