sigaction() 更可靠,它是 POSIX 标准接口,可精确控制信号掩码、系统调用重启(SA_RESTART)等,而 signal() 行为不一致且易导致处理函数重置。

signal() 和 sigaction() 哪个更可靠
signal() 看起来简单,但行为在不同系统上不一致:Linux 下可能重置信号处理函数,且不保证中断的系统调用自动重启;sigaction() 是 POSIX 标准接口,能精确控制信号掩码、是否重启被中断的系统调用(SA_RESTART)、是否阻塞其他信号等,生产环境必须用它。
常见错误是用 signal() 注册 SIGINT 后,Ctrl+C 多按几次就失效——这正是信号处理函数被重置导致的。
- 永远优先使用
sigaction(),不要依赖signal() - 注册前清零
struct sigaction,避免未初始化字段引发未定义行为 - 设置
sa_flags |= SA_RESTART,否则read()、accept()等可能返回-1并设errno = EINTR - 若需在信号处理中做复杂操作(如写日志、释放资源),应只设一个
volatile sig_atomic_t标志位,主循环检查并处理
信号处理函数里能安全调用哪些函数
绝大多数 C/C++ 标准库函数不是异步信号安全的(async-signal-safe)。比如 printf()、malloc()、std::cout、new 都禁止在信号处理函数中调用——它们可能锁内部状态,导致死锁或崩溃。
POSIX 明确列出可安全调用的函数,常用仅几个:write()、read()、sigprocmask()、raise(),以及原子操作相关函数。C++ 中连 std::atomic 的部分操作也不保证信号安全(除非明确标注为 async-signal-safe)。
- 只用
write(STDERR_FILENO, ...)打印紧急信息,别用流或格式化函数 - 避免任何内存分配、锁、IO 流、STL 容器操作
- 想记录时间?提前缓存好字符串,或用
clock_gettime(CLOCK_MONOTONIC, ...)(它是 async-signal-safe) - 真正需要复杂逻辑时,用自管道(self-pipe trick)或
signalfd()(Linux 特有)把信号转成文件描述符事件
如何让主线程等待信号而不是忙等
直接 while(1) 空转浪费 CPU,且无法响应信号(除非用 pause() 或 sigsuspend())。正确做法是阻塞信号,再用 sigsuspend() 暂停线程,直到指定信号到达。
注意:pause() 无法指定信号集,且被任意捕获信号唤醒后会重新阻塞——容易漏信号;sigsuspend() 更可控,它临时替换信号掩码并原子地挂起线程。
- 先用
sigprocmask(SIG_BLOCK, &set, nullptr)阻塞目标信号(如SIGTERM) - 构造新的信号集,用
sigdelset()移除要等待的那个信号 - 循环调用
sigsuspend(&temp_set),它返回后说明信号已送达 - 退出循环后,记得用
sigprocmask(SIG_UNBLOCK, &set, nullptr)解除阻塞(如果后续还需响应)
多线程下信号捕获要注意什么
信号是发给进程的,但由「未阻塞该信号的任意线程」接收。默认所有线程都继承主进程的信号掩码,所以哪个线程恰好没屏蔽 SIGUSR1,它就可能收到——结果不可预测。
典型问题:主线程注册了 SIGUSR1 处理函数,但子线程正在执行 malloc() 时被中断,处理函数又调用了 write() ——看似安全,但如果该线程同时持有了 malloc 的内部锁,就会死锁。
- 创建子线程前,用
pthread_sigmask()主动屏蔽所有信号(SIGKILL和SIGSTOP除外) - 只在主线程中解除屏蔽并调用
sigsuspend()或sigwait() - 用
sigwait()替代信号处理函数:它让线程同步等待信号,完全规避异步上下文问题 - 避免在非主线程中调用
signal()或sigaction(),它们对整个进程生效,但效果取决于哪个线程实际接收
信号处理最棘手的不是注册,而是确保整个生命周期内信号不会落在不安全的上下文中。哪怕逻辑再简单,只要涉及多线程、系统调用重启、或跨平台部署,sigaction() + sigsuspend() + 单线程信号接收就是绕不开的组合。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











