std::condition_variable 不能直接响应信号,必须通过 sigwait 等机制在普通线程中捕获信号后修改原子标志并 notify;需用 wait_for 配合 predicate 检查标志与业务条件,并在唤醒后及时重置标志。

std::condition_variable 不能直接响应信号?
没错,std::condition_variable 本身不监听 OS 信号(如 SIGINT、SIGUSR1),它只配合 std::mutex 和 wait/notify 实现线程间同步。想用它“响应信号”,必须把信号捕获和条件变量唤醒拆成两步:先用 sigwait 或信号处理函数通知某个共享状态,再由等待线程感知该状态变化。
常见错误是直接在 signal handler 里调用 cv.notify_one() —— 这是未定义行为,因为 signal handler 中只能调用异步信号安全函数,而 std::condition_variable::notify_one 不在安全列表里。
- 正确做法:用
signalfd(Linux)或sigwait(POSIX)在**普通线程中阻塞等待信号**,收到后修改原子标志或加锁通知condition_variable - 避免用
signal+handler直接触发 notify - 若跨平台,优先选
sigwait(需设信号为阻塞态,用pthread_sigmask)
如何用 sigwait + condition_variable 构建可中断的 wait
核心思路是让一个专用信号接收线程调用 sigwait,等信号到来后设置一个 std::atomic<bool></bool> 标志,并调用 cv.notify_all()。其他线程用带超时的 cv.wait_until 或 cv.wait_for 等待该标志或业务条件。
std::atomic<bool> g_signal_received{false};
std::mutex g_mutex;
std::condition_variable g_cv;
// 信号接收线程
void signal_waiter() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &set, nullptr); // 阻塞 SIGUSR1
int sig;
while (sigwait(&set, &sig) == 0) {
if (sig == SIGUSR1) {
g_signal_received = true;
std::lock_guard<:mutex> lk(g_mutex);
g_cv.notify_all();
}
}
}</:mutex></bool>
注意:sigwait 要求信号在调用前已被线程阻塞(不是忽略),否则可能丢失或触发默认动作。
wait_for 中怎么区分是超时还是信号唤醒?
不能只靠 cv.wait_for 返回值判断——它返回 false 只表示超时,但若信号在超时前到达,也可能因竞争导致你读到旧状态。必须把信号事件和业务逻辑解耦,用显式状态检查:
- 用
g_signal_received.load()在wait_for的 predicate 中检查 - 或把信号作为“中断请求”,在 predicate 中同时检查业务就绪条件和中断标志
- 唤醒后务必重置
g_signal_received(如果只响应一次),否则下次wait会立即返回
示例片段:
std::unique_lock<:mutex> lk(g_mutex);
g_cv.wait_for(lk, 5s, []{ return g_signal_received.load() || some_work_ready(); });
if (g_signal_received.exchange(false)) {
// 处理信号中断
} else if (some_work_ready()) {
// 处理业务
}</:mutex>
为什么不用 std::condition_variable::wait 而用 wait_for/wait_until?
因为 wait 是无条件阻塞,一旦被 notify 唤醒就必须重新检查 predicate;而信号是外部异步事件,你无法保证它和业务条件的时序。用 wait_for 至少能防止单次等待无限挂起,也便于做心跳、超时清理等逻辑。
另一个关键是:即使没信号,你也需要定期检查某些状态(比如连接是否断开、任务是否被取消)。纯 wait 容易掩盖这类轮询需求。
真正难的不是写 notify,而是设计好「谁负责清标志」「何时重置原子变量」「多个信号是否累积」——这些细节不处理好,就会出现唤醒丢失或重复响应。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











