C++多线程与信号signal

36 篇文章 1 订阅
7 篇文章 0 订阅
  1. 信号是进程间通信的方式, 比如我们kill命令可以给某个进程直接发送SIGINT或SIGTERM等信号。
  2. 可以在代码中handle中某种信号,具体有两种办法:signal()sigaction().
  3. 给某一个线程注册signal handle函数相当于给所有线程注册,并且后注册的会覆盖之前的。

看一个例子:

#include <thread>
#include <stdexcept>
#include <iostream>
#include <unistd.h>
#include <signal.h>

static void handler(int sig){
    std::cout << "111 Got signal. sig: " << sig << std::endl;
    throw std::runtime_error("test");
}

static void handler2(int sig){
    std::cout << "222 Got signal. sig: " << sig << std::endl;
    throw std::runtime_error("test");
}

void threadRun()
{
/*
    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset( &sa.sa_mask );
    sa.sa_handler = &handler;
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return;
    }
*/
    while(true) {
        std::cout << "thread running\n";
        sleep(1);
    }
}
int main( int argc, char *argv[] )
{
    std::thread t( threadRun );

try{
	// signal action
    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset( &sa.sa_mask );
    sa.sa_handler = &handler2;
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return EXIT_FAILURE;
    }

    while(true) {
        std::cout << "main thread running\n";
        sleep(1);
    }
}
catch( std::exception& ex ){
    std::cout << "exception: " << ex.what() << std::endl;
}
    t.join();
	return 0;
}

这个程序需要两个Ctrl+C才能杀死。 第一个SIGINT被主线程handle主, handle函数中跑出异常被主线程catch, 然后主线程会等在t.join()处。第二个SIGINT会被子线程handle, 抛出异常, 上文C++多线程与异常中说到,线程中抛出的异常如果没被catch, 会导致进程终止。

  1. 这里有一个疑问, 我查遍文档, C++或Linux标准中没有说进程的signal 会被那个线程处理, 我测试过每次都是先被主线程处理, 然后依次按创建先后顺序,选择线程来处理signal (排除signal mask的情况)。

A signal may be generated (and thus pending) for a process as a whole
(e.g., when sent using kill(2)) or for a specific thread (e.g.,
certain signals, such as SIGSEGV and SIGFPE, generated as a
consequence of executing a specific machine-language instruction are
thread directed, as are signals targeted at a specific thread using
pthread_kill(3)). A process-directed signal may be delivered to any
one of the threads that does not currently have the signal blocked. If
more than one of the threads has the signal unblocked, then the kernel
chooses an arbitrary thread to which to deliver the signal.

可以再看一个例子:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

void *threadfn1(void *p){
    while(1){
        printf("thread1\n");
        sleep(1);
    }
    return 0;
}

void *threadfn2(void *p){
    while(1){
        printf("thread2\n");
        sleep(1);
    }
    return 0;
}

void *threadfn3(void *p){
    while(1){
        printf("thread3\n");
        sleep(1);
    }
    return 0;
}

void handler(int signo, siginfo_t *info, void *extra) {
    printf("Got signal. sig: %d\n", signo );
    int i;
    for(i=0;i<10;i++)
    {
        puts("signal handler");
        sleep(1);
    }
}

void set_sig_handler(void){
        struct sigaction action;
        action.sa_flags = SA_SIGINFO; 
        action.sa_sigaction = handler;
        // # kill -37 [pid]
        //if (sigaction(SIGRTMIN + 3, &action, NULL) == -1) { 
        if (sigaction(SIGINT, &action, NULL) == -1) { 
            perror("sigusr: sigaction");
            _exit(1);
        }
}

int main()
{
    pthread_t t1,t2,t3;
    set_sig_handler();
    pthread_create(&t1,NULL,threadfn1,NULL);
    pthread_create(&t2,NULL,threadfn2,NULL);
    pthread_create(&t3,NULL,threadfn3,NULL);

    //pthread_exit(NULL);
    pthread_t t0 = pthread_self();
    printf("t0:%ld\nt1:%ld\nt2:%ld\nt3:%ld\n",t0,t1,t2,t3);
    while(1){
        printf("thread0\n");
        sleep(1);
    }
    //sleep(50);
    return 0;
}

可以验证handle signal的顺序为t0 > t1 > t2 >t3

  1. 虽然线程之间共享信号, 但是每个线程可以各自屏蔽一些信号

POSIX.1 also requires some attributes to be distinct for each thread, including:
signal mask (pthread_sigmask(3))
alternate signal stack (sigaltstack(2))

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<signal.h>
 
void mask_sig(void){
    sigset_t mask;
    sigemptyset(&mask); 
        sigaddset(&mask, SIGINT); 
        pthread_sigmask(SIG_BLOCK, &mask, NULL);
}

void *threadfn1(void *p){
    mask_sig();
    while(1){
        printf("thread1\n");
        sleep(2);
    }
    return 0;
}
 
void *threadfn2(void *p){
    mask_sig();
    while(1){
        printf("thread2\n");
        sleep(2);
    }
    return 0;
}
 
void *threadfn3(void *p){
    while(1){
        printf("thread3\n");
        sleep(2);
    }
    return 0;
}
 
void handler(int signo, siginfo_t *info, void *extra) {
    int i;
    for(i=0;i<10;i++){
        puts("signal");
        sleep(2);
    }
}
 
void set_sig_handler(void){
        struct sigaction action; 
        action.sa_flags = SA_SIGINFO; 
        action.sa_sigaction = handler;
        if (sigaction(SIGINT, &action, NULL) == -1) { 
            perror("sigusr: sigaction");
            _exit(1);
        }
}
 
int main()
{
    pthread_t t1,t2,t3;
    set_sig_handler();
    pthread_create(&t1,NULL,threadfn1,NULL);
    pthread_create(&t2,NULL,threadfn2,NULL);
    pthread_create(&t3,NULL,threadfn3,NULL);
    pthread_exit(NULL);
    return 0;
}

sigprocmask() 和 pthread_sigmask()的区别是前者只能在单线程的进程中使用。

Ref:
https://www.xspdf.com/resolution/55857746.html
https://unix.stackexchange.com/questions/225687/what-happens-to-a-multithreaded-linux-process-if-it-gets-a-signal
https://devarea.com/linux-handling-signals-in-a-multithreaded-application/#.X9Law0NS9hE

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值