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

Unix Shell无法传递CTRL+C信号给高负载多进程C++程序的问题

问题描述

我开发了一个多线程C++程序,主线程完成初始化后创建多个子进程处理RabbitMQ的消息,主线程采用如下方式实现忙等待:

while (rmqbag_active) {
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

我已为SIGINT注册信号处理函数:

std::signal(SIGINT, signal_handler);
void signal_handler([[maybe_unused]] int signal) {
  rmqbag_active = false;
  rmqbag::log_debug("Received stopping signal");
}

当子进程负载较低时,程序运行正常;但当消息量过高(4个子进程每秒接收约1750条消息)时,按下CTRL+C无任何反应,程序未收到信号(无日志输出)。
手动通过HTOP向进程发送SIGINT时,程序可正常处理并退出。我已尝试多个终端模拟器及bash、zsh、fish shell,现象一致;也试过用nanosleep替代this_thread::sleep_for实现忙等待,还为子进程注册了信号处理函数,但均无效。子进程无日志输出,不会造成终端消息刷屏。

请问为何终端无法向进程发送停止信号?


问题分析与解决方案

核心原因

终端按下CTRL+C时,会向前台进程组的所有进程发送SIGINT,而非单个进程。高负载下出现无响应的情况,通常是以下原因导致:

  1. 进程组前台状态丢失:高负载时,某个子进程或主线程的系统调用(如RabbitMQ客户端的IO操作)可能意外导致整个进程组脱离前台状态,终端的SIGINT无法送达。
  2. 信号被意外屏蔽:RabbitMQ客户端库在高负载处理消息时,可能调用底层IO接口(如epoll_wait、recv),这些接口可能临时修改进程的信号掩码,屏蔽SIGINT。
  3. 全局变量的线程/进程安全问题:rmqbag_active未声明为原子类型,信号处理函数的修改无法被主线程及时感知,但这只会导致退出延迟,不会完全阻止信号接收,不过高负载会放大该问题。

修复步骤

  1. 确保全局变量的原子性:将rmqbag_active声明为std::atomic<bool>,避免缓存一致性导致的主线程无法及时读取修改:
    std::atomic<bool> rmqbag_active = true;
    
  2. 检查并恢复进程组前台状态:在主线程的循环中定期检查当前前台进程组是否与自身进程组一致,若不一致则尝试恢复:
    #include <unistd.h>
    #include <sys/ioctl.h>
    
    while (rmqbag_active) {
        pid_t current_pgrp = tcgetpgrp(STDIN_FILENO);
        if (current_pgrp != getpgrp()) {
            tcsetpgrp(STDIN_FILENO, getpgrp());
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    
  3. 替换忙等待为条件变量:用std::condition_variable替代睡眠等待,信号处理函数直接唤醒主线程,避免睡眠期间的信号延迟:
    std::atomic<bool> rmqbag_active = true;
    std::mutex mtx;
    std::condition_variable cv;
    
    void signal_handler([[maybe_unused]] int signal) {
        rmqbag_active = false;
        cv.notify_one();
        rmqbag::log_debug("Received stopping signal");
    }
    
    // 主线程等待逻辑
    int main() {
        // 初始化、注册信号、创建子进程...
        std::unique_lock<std::mutex> lk(mtx);
        cv.wait(lk, []{ return !rmqbag_active; });
        // 清理资源、退出
        return 0;
    }
    
  4. 排查RabbitMQ客户端的信号干扰:检查客户端库是否有默认信号处理逻辑,或在高负载下修改信号掩码。可在信号注册后,用sigprocmask检查SIGINT是否被屏蔽:
    #include <signal.h>
    
    sigset_t mask;
    sigprocmask(SIG_BLOCK, nullptr, &mask);
    if (sigismember(&mask, SIGINT)) {
        rmqbag::log_debug("SIGINT is masked");
    }
    
  5. 避免子进程重复注册信号:信号是发送给进程组的,仅需在主线程注册一次信号处理函数即可,子进程重复注册可能导致信号处理逻辑混乱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 00:27:20