linux下信号处理器默认仅对触发信号的线程生效,子线程崩溃(如sigsegv)不会自动通知主线程;需通过pthread_sigmask屏蔽信号+sigwait统一处理,或用std::exception_ptr跨线程传递c++异常,后者更安全可靠。

子线程崩溃时主线程无法直接捕获信号
Linux/Unix 下,signal 和 sigaction 注册的信号处理器默认只对**接收该信号的线程生效**。子线程触发 SEGV、ABRT 等同步信号(如空指针解引用)时,信号会递送到出错线程自身——如果子线程没设 handler,就会直接终止,主线程完全感知不到。这不是“捕获不到”,而是信号根本没发给主线程。
用 pthread_sigmask 阻塞子线程信号 + sigwait 在主线程统一处理
核心思路:让所有线程(包括子线程)都屏蔽指定信号,再由主线程主动调用 sigwait 等待并处理。这要求信号是异步发送的,但对同步错误(如 SEGV)需额外转换。
实操要点:
- 主线程在创建子线程前,用
pthread_sigmask(SIG_BLOCK, &set, nullptr)屏蔽SIGSEGV、SIGABRT等目标信号 - 每个子线程启动后立即执行相同
pthread_sigmask(SIG_BLOCK, &set, nullptr),确保继承的信号掩码生效(注意:线程不自动继承掩码,必须显式设置) - 主线程用
sigwait(&set, &sig)阻塞等待——此时任何线程触发的被屏蔽信号都会转为“挂起状态”,由sigwait摘取 - ⚠️ 关键限制:
sigwait只能处理**标准信号**,且SEGV这类同步信号默认不能被sigwait捕获;必须先用sigaltstack+sigaction将其转为可等待形式,或改用signalfd(Linux 特有)
更可靠的做法:子线程内部抛异常 + 主线程 std::thread::join 后检查状态
C++ 标准线程模型不支持跨线程传递信号,但允许子线程用 std::exception_ptr 把异常安全地传回主线程。这是最符合 C++ 习惯、无需系统信号干预的方式。
示例关键片段:
std::exception_ptr eptr;
std::thread t([&eptr] {
try {
risky_operation(); // 可能 throw
} catch (...) {
eptr = std::current_exception(); // 捕获并保存
}
});
t.join();
if (eptr) {
std::rethrow_exception(eptr); // 在主线程重新抛出
}
注意事项:
- 必须在
t.join()后检查eptr,不能在子线程运行中读——std::exception_ptr是线程安全的,但变量本身不是原子的 - 若子线程因未捕获异常而终止,
std::thread析构时会调用std::terminate,程序直接退出——所以子线程里一定要有顶层catch(...) - 这种方法对
abort()、exit()或段错误仍无效,它只管 C++ 异常
真正需要拦截段错误?用 sigaction + sigaltstack 在主线程注册 handler
如果业务确实要求主线程响应子线程的 SEGV(例如做 crash dump),唯一办法是让主线程安装全局信号处理器,并确保它能在任何线程触发 SEGV 时被调用。但这极其危险:
- 信号处理器中只能调用异步信号安全函数(
write、_exit等),不能用std::cout、malloc、printf,否则行为未定义 - 必须提前设置备用栈(
sigaltstack),否则在子线程栈溢出时,handler 无栈可执行 - handler 中无法安全调用
std::thread::join或锁操作——你只能记录信息、写文件、然后_exit - 现代 glibc 对多线程下
SEGV的投递目标有优化,不保证总到主线程;依赖此机制属于未定义行为
实际工程中,这类需求几乎总是该交给外部工具(gdb --pid、systemd-coredump、google-breakpad)而非自己实现信号捕获。
真正难处理的从来不是“怎么把信号转给主线程”,而是“信号来了之后,你敢在 handler 里做什么”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











