You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++多线程中多线程触发同一信号时如何仅执行一次信号处理并屏蔽信号?

多线程程序中信号处理的两个问题解决

问题背景

在C++多线程程序中,多个线程同时崩溃会触发SIGABRT信号,期望仅执行一次信号处理逻辑(打印错误后退出),但使用sigaction()设置处理函数后,多个线程仍会同时进入处理函数。同时需要明确:多线程程序中如何在信号处理过程中屏蔽指定信号。

问题分析

  1. 多线程重复进入信号处理函数的原因:

    • 每个线程独立维护自身的信号掩码,sigaction的sa_mask仅能阻止当前执行处理函数的线程再次收到指定信号,无法阻止其他线程触发的信号。
    • 原代码无任何互斥或原子校验逻辑,导致所有触发信号的线程都会进入处理函数。
  2. 信号处理过程中屏蔽信号的机制:

    • sigaction的sa_mask字段定义了信号处理函数执行期间,会被临时添加到当前线程信号掩码中的信号集合,这些信号会被阻塞直到处理函数返回,但该作用范围仅限当前执行处理的线程,而非整个进程。

解决方案

1. 确保信号处理逻辑仅执行一次

使用原子布尔变量作为全局标志,在信号处理函数中先校验标志:若未处理过则执行逻辑并设置标志,否则直接返回。同时注意,信号处理函数中只能调用异步信号安全的函数(如write、_exit,printf并非完全异步安全,建议替换为write)。

修改后的代码示例:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <atomic>
#include <sys/syscall.h>
#include <string.h>

#define gettid() syscall(__NR_gettid)
#define THREAD_NUM 10

// 原子标志:确保仅执行一次信号处理
std::atomic<bool> g_signal_handled(false);

int Signal(int signo, void (*func)(int))
{
    struct sigaction act;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);  
    // 处理期间屏蔽常见崩溃信号
    sigaddset(&act.sa_mask, SIGABRT);
    sigaddset(&act.sa_mask, SIGBUS);
    sigaddset(&act.sa_mask, SIGSEGV);
    sigaddset(&act.sa_mask, SIGILL);
    sigaddset(&act.sa_mask, SIGFPE);

    act.sa_flags = 0;
    return sigaction(signo, &act, NULL);
}

void SigHandler(int signal){
    // 检查是否已处理过信号,避免重复执行
    if (g_signal_handled.exchange(true)) {
        return;
    }

    // 使用write替代printf,保证异步信号安全
    char msg[64];
    int len = snprintf(msg, sizeof(msg), "tid: %ld, access signal: %d\n", gettid(), signal);
    write(STDOUT_FILENO, msg, len);
    
    // 退出进程,所有线程都会终止
    _exit(1);
}

void* worker(void* args){
    for(;;){
        int a = std::stoi("");
    }
}

int main(){
    Signal(SIGBUS, SigHandler);
    Signal(SIGSEGV, SigHandler);
    Signal(SIGABRT, SigHandler);
    Signal(SIGILL, SigHandler);
    Signal(SIGFPE, SigHandler);

    pthread_t pids[THREAD_NUM];
    for(int i = 0; i < THREAD_NUM; i++){
        int err = pthread_create(&pids[i], NULL, worker, NULL);
        if(err != 0){
            perror("pthread_create err");
            return -1;
        }
    }

    for(int i = 0; i < THREAD_NUM; i++){
        pthread_join(pids[i], NULL);
    }
    return 0;
}

2. 多线程中信号处理过程中屏蔽指定信号

有两种常用实现方式:

  • 利用sigaction的sa_mask:
    这是最推荐的方式,调用sigaction时通过sa_mask指定需要在处理函数执行期间屏蔽的信号。当信号处理函数被调用时,系统会自动将这些信号添加到当前线程的信号掩码中,处理完成后恢复原掩码。比如原代码中设置的sa_mask,会在处理SIGABRT时,让当前线程阻塞SIGABRT、SIGBUS等信号,避免同一线程重复触发。

  • 在信号处理函数中手动修改线程信号掩码:
    使用pthread_sigmask(线程级,多线程场景优先)手动添加需要屏蔽的信号,该函数是异步信号安全的,可在信号处理函数中调用。示例片段:

    void SigHandler(int signal){
        sigset_t mask;
        sigemptyset(&mask);
        sigaddset(&mask, SIGABRT);
        sigaddset(&mask, SIGSEGV);
        // 屏蔽指定信号,SIG_BLOCK表示添加到当前掩码
        pthread_sigmask(SIG_BLOCK, &mask, NULL);
    
        // 处理逻辑...
    
        // 恢复原掩码(若处理后直接退出则无需恢复)
        pthread_sigmask(SIG_UNBLOCK, &mask, NULL);
        _exit(1);
    }
    

编译与运行验证

编译命令:

g++ test_sto.cpp -g -Wall -std=c++11 -lpthread -o test_sto

运行后只会输出一条信号处理日志,说明仅执行了一次处理逻辑。

内容的提问来源于stack exchange,提问作者Karate Yuan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 05:18:20