std::condition_variable是跨线程通知首选机制,因其轻量、可靠、零cpu消耗,天然支持多生产者/消费者,配合mutex与谓词可避免竞态。

std::condition_variable 为什么是跨线程通知的首选机制
直接用 std::condition_variable 配合 std::mutex 实现观察者模式中的“等待-唤醒”逻辑,比轮询或信号量更轻量、更可靠。它天然支持多生产者/单消费者或多消费者场景,且不消耗 CPU。
关键点在于:通知方(被观察对象)调用 notify_one() 或 notify_all(),等待方(观察者)在 wait() 中挂起并自动释放锁,被唤醒后重新竞争锁并检查谓词——这个谓词必须是共享状态的原子读取或受同一互斥量保护的访问。
- 不要在
wait()外部单独加锁再检查状态,否则存在竞态:状态变更和进入 wait 之间可能丢通知 - 谓词建议用 lambda,例如
[&] { return state_changed; },避免函数对象捕获失效 -
notify_all()更安全,尤其当多个观察者等待同一事件但逻辑上需全部响应时;notify_one()适合“只唤醒一个处理者”的任务队列场景
如何让 Observer 在非主线程中安全回调
观察者回调函数不能假设运行在线程 A,而被观察对象变更发生在另一个线程 B——这是常见崩溃根源。必须把回调调度到目标线程执行,典型做法是“队列 + 线程循环”。
例如每个观察者持有一个 std::queue<:function>></:function> 和专属线程,主循环用 std::condition_variable 等待新任务;被观察对象通过 push() + notify_one() 投递回调。注意队列操作必须加锁,且 push 后立即 notify,避免漏唤醒。
- 别用
std::async(std::launch::async, ...)替代专用线程——生命周期不可控,容易堆积未执行任务 - 如果观察者数量少且对延迟敏感,也可由被观察对象直接调用回调,但必须确保回调内部不访问任何仅属于调用线程的资源(如 UI 句柄、TLS 变量)
- 避免在回调中长时间阻塞,否则会卡住整个观察者线程
std::shared_ptr + weak_ptr 如何防止观察者被提前析构
多线程下,观察者对象可能在被通知前就被销毁,而被观察对象仍持有其裸指针或强引用,导致回调时访问已释放内存。用 std::weak_ptr 存储观察者是标准解法。
被观察对象保存 std::vector<:weak_ptr>></:weak_ptr>,每次通知前调用 lock() 获取 std::shared_ptr,返回空则说明观察者已析构,跳过该回调。
- 注册观察者时,传入
std::shared_ptr<observer></observer>,内部转存为weak_ptr - 不要在
lock()成功后长期持有shared_ptr——仅用于本次回调调用即可 - 若观察者析构函数中又尝试反注册自身,需额外用
std::atomic<bool></bool>标记“正在析构”,避免反注册时遍历 vector 的同时被其他线程修改
std::atomic 能否替代 condition_variable 做简单通知
可以,但仅限最简场景:单次状态变更、最多一个等待者、不关心等待期间是否被中断或超时。比如初始化完成标志、开关信号。
典型写法是等待方循环 while (!flag.load(std::memory_order_acquire)) { std::this_thread::yield(); },通知方写 flag.store(true, std::memory_order_release)。但这会空转 CPU,且无法优雅响应中断或设置超时。
- 一旦有多个等待者或需要精确控制唤醒时机,立刻换回
std::condition_variable -
std::atomic_flag比std::atomic<bool></bool>更轻量,但只支持 test-and-set,不适合做“可重置通知” - 所有原子操作的 memory order 必须匹配:release-store 对应 acquire-load,否则编译器/CPU 可能重排指令导致逻辑错乱
跨线程状态通知真正难的不是语法,而是厘清谁拥有状态、谁负责清理、谁决定回调在哪执行——这些边界一旦模糊,condition_variable 写得再标准也会崩。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











