多线程中 signal 不可靠,因信号仅随机递送给某一线程且无法指定;应屏蔽所有线程的信号,由专用线程调用 sigwaitinfo() 同步等待,再通过线程安全机制转发事件。

为什么 signal 在多线程里基本不可靠
多线程下用 signal 捕获异步事件(比如 SIGUSR1)极大概率会失败——信号只递送给「某个」线程,且无法指定是哪一个。Linux 的 POSIX 信号模型不保证发送给主线程或注册了 signal() 的线程;更糟的是,如果多个线程都调用了 sigprocmask() 或 pthread_sigmask(),信号可能被屏蔽、丢失,甚至触发未定义行为(如 abort())。
常见错误现象:signal(SIGUSR1, handler) 注册后收不到信号;程序在无提示情况下崩溃;gdb 中单步时信号被吞掉;strace -e trace=signal 显示信号已送达但 handler 没执行。
- 不要在任意线程中调用
signal()或sigaction()—— 它们是进程级接口,不感知线程上下文 - 避免用
raise()或kill(getpid(), ...)测试:这会把信号发给整个进程,调度不确定性更高 - 真正可控的方式是让**唯一一个线程专门等待信号**,其他线程通过线程安全机制与之通信
用 sigwaitinfo() + pthread_sigmask() 构建信号接收线程
POSIX 提供了线程安全的信号等待方案:先屏蔽目标信号(对所有线程),再由指定线程调用 sigwaitinfo() 同步等待。这是目前 C++ 多线程中最可靠的做法。
关键点:
- 在
main()开始处用pthread_sigmask()屏蔽信号(不是sigprocmask(),后者只影响调用线程,且在多线程中行为未定义) - 启动一个专用线程,在其中循环调用
sigwaitinfo();它会阻塞直到信号到达,返回后可安全调用非异步信号安全函数(如std::queue::push()、std::condition_variable::notify_one()) - 用
std::atomic<bool></bool>或std::mutex配合std::condition_variable把信号事件转发给业务逻辑线程
示例片段(简化):
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &set, nullptr); // 屏蔽所有线程
std::thread sig_thread([]{
int sig;
siginfo_t info;
while (true) {
sig = sigwaitinfo(&set, &info);
if (sig == SIGUSR1) {
// 安全地通知工作线程
std::lock_guard<:mutex> lk(q_mutex);
signal_queue.push(info.si_value.sival_int);
q_cv.notify_one();
}
}
});
</:mutex>
调试时如何确认信号真的被送到了正确线程
光看 gdb 的 info threads 不够——你需要验证信号掩码和等待状态。最直接的方法是用 ps 和
/proc/PID/status:
- 运行中执行
ps -o pid,tid,comm,wchan -T <pid>,观察哪个线程的 <code>wchan是do_signal或sigwaitinfo,说明它正在等待信号 - 检查
/proc/<tid>/status</tid>中的SigBlk字段:对应信号位应为 1(十六进制),确认屏蔽生效 - 用
kill -USR1 <pid></pid>发送后,立刻cat /proc/<tid>/status | grep Sig</tid>,看SigPnd(pending)是否清零、ShdPnd(shared pending)是否变化
注意:gdb 默认会拦截并停止所有线程响应信号,调试时加 handle SIGUSR1 nostop noprint pass,否则信号会被 gdb 拦截而 never 到达目标线程。
替代方案:别用信号,改用 eventfd() 或 pipe()
如果控制权在你手上(比如你写发送端),强烈建议绕过信号机制。Linux 下 eventfd() 是专为线程/进程间事件通知设计的,比信号更轻量、更可预测:
-
eventfd(0, EFD_CLOEXEC)创建一个 8 字节计数器 fd - 一端
write()一个 uint64_t 值(如 1),另一端read()或用epoll_wait()等待 - 完全线程安全,无信号语义陷阱,还能和
std::thread+std::future自然集成 - Windows 上可用
WaitForMultipleObjects()+CreateEvent()类比实现
信号本质是“中断式”接口,而现代 C++ 多线程编程需要的是“协作式”事件流。强行把 signal 塞进 std::thread 生态,就像给电动车装化油器——能转,但抖得厉害,还容易熄火。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











