signal()捕获的是操作系统信号而非c++异常,二者机制完全不同;应使用sigaction()替代已弃用的signal(),多线程下须用pthread_sigmask()和sigwait()确保信号由指定线程安全处理。

signal() 捕获的是信号,不是 C++ 异常
很多人写 signal(SIGSEGV, handler) 后发现 throw 不触发它,是因为信号(如 SIGSEGV、SIGABRT)和 C++ 异常(throw / catch)完全无关——前者是操作系统发给进程的异步通知,后者是语言级的同步控制流。混用它们会导致未定义行为,比如在信号处理函数里调用 std::cout 或 new 就可能崩溃。
常见错误现象:signal() 注册后程序仍直接 core dump;handler 里调用 std::string 构造函数卡死;多线程下信号被随机线程接收。
- 只在 handler 中做「信号安全」操作:比如写入
write()、_exit()、修改volatile sig_atomic_t变量 - 避免在 handler 里调用任何 STL、malloc/new、printf 系列、锁相关函数
- 若需响应信号后做复杂逻辑,用
sigwait()+ 单独线程更可控(见下一条)
用 sigwait() 在专用线程里等信号更可靠
signal() 是全局注册、异步送达、不可预测执行时机;而 sigwait() 是同步阻塞等待,只在指定线程中生效,且能保证信号不会中断其他线程的系统调用(比如 read() 不会因 SIGINT 被打断返回 -1 并设 errno=EINTR)。
使用场景:服务程序需要优雅退出(SIGTERM)、调试时捕获 SIGUSR1 触发日志转储、避免主线程被信号打断关键路径。
立即学习“C++免费学习笔记(深入)”;
- 先用
sigprocmask()在所有线程中屏蔽目标信号(除一个专用信号线程) - 新建线程,调用
sigwait(&set, &sig)阻塞等待 - 收到信号后,可安全调用
std::thread、std::queue、日志库等非信号安全函数 - 注意:Linux 下
sigwait()要求信号已在当前线程被屏蔽,否则行为未定义
std::signal() 在 C++11 后基本弃用,优先用 sigaction()
std::signal()(即 C 的 signal())语义模糊:不同平台对信号是否自动重置、是否重启被中断的系统调用(SA_RESTART)行为不一致;而且它不能设置信号掩码,无法防止 handler 执行期间被其他信号打断。
性能与兼容性影响:glibc 默认用 signal() 实现为 sigaction() 加一层封装,但某些嵌入式 libc(如 musl)或旧内核上可能丢失信号;C++ 标准明确标注 std::signal 为 deprecated。
- 改用
sigaction():显式控制sa_flags(如SA_RESTART、SA_NODEFER)、设置sa_mask屏蔽临时信号 - 不要依赖
signal()返回值判断是否成功,它失败时返回SIG_ERR但不设errno;sigaction()失败才设errno - 示例关键片段:
struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGINT, &sa, nullptr);
多线程下 signal() 几乎无效,必须用 pthread_sigmask + sigwait
POSIX 规定:信号是发给「进程」的,但由「某个线程」接收。默认情况下,哪个线程收信号是不确定的(尤其 SIGSEGV 总发给触发它的线程,但 SIGUSR2 可能被任意线程收到)。你在主线程调 signal(),不代表 handler 就在那里运行——很可能在 worker 线程里执行,导致访问局部变量崩溃。
容易踩的坑:std::thread 创建后没调 pthread_sigmask() 屏蔽信号,结果新线程意外收到 SIGPIPE 直接终止;多个线程都调 signal(),行为互相覆盖。
- 启动时用
pthread_sigmask(SIG_BLOCK, &set, nullptr)屏蔽所有线程的信号(除一个管理线程) - 确保只有那个管理线程调
sigwait(),其他线程保持屏蔽状态 - 不要在
std::thread构造函数里直接调signal()—— 它属于整个进程,不是线程局部 - 如果必须让某 worker 线程处理特定信号(如
SIGUSR1),需在该线程内调pthread_sigmask()解除屏蔽,再sigwait()
信号处理真正难的不是注册函数,而是厘清「谁发、谁收、谁处理、是否可重入、是否干扰正常流程」——这些细节一旦错位,问题往往延迟暴露,且难以复现。










