为何修改条件变量的条件时必须持有互斥锁?
为什么修改atomic条件变量时也需要持有互斥锁?
你提出的写法在简单测试中能运行,但并不符合C++条件变量的正确使用范式,cppreference的要求本质是为了避免两类问题:虚假唤醒导致的逻辑错误,以及复杂条件下的原子性与可见性问题。
1. 你忽略的虚假唤醒风险
你的测试代码中thread_1的wait没有使用谓词重载,这本身就是错误的写法:
// 错误写法:无谓词的wait cv.wait(lock);
条件变量存在虚假唤醒的可能(操作系统会因某些原因唤醒等待线程,并非因为notify调用)。如果线程1被虚假唤醒时,线程2还未修改condition,线程1会直接返回,此时condition仍为false,完全违背了等待逻辑的初衷。
正确的写法必须使用带谓词的wait,确保被唤醒后重新检查条件:
// 正确写法:带谓词的wait cv.wait(lock, []{ return condition.load(); });
2. 为什么修改条件必须持有锁?
即使condition是atomic类型,cppreference要求修改时持有互斥锁,核心原因有两点:
- 统一范式,避免复杂场景出错:如果条件是基于非线程安全的共享数据(比如队列、计数器),修改时必须持有锁来保证原子性。要求atomic变量也持有锁,是为了让开发者养成统一的习惯,避免在复杂条件下因疏忽导致数据竞争。
- 确保条件修改与notify的时序一致性:虽然你的写法中,
condition.store(true)和notify_one的顺序不会被重排序(atomic的release语义+互斥锁的acquire语义保证了happens-before关系),但在多线程修改条件的场景下,不持有锁会导致条件的修改和notify的调用无法形成原子操作,可能出现多次修改只触发一次notify的情况,导致部分等待线程永久阻塞。
3. 你的测试代码“正常”的原因
你的测试场景非常单一:每次只启动一对线程,线程2总是先修改条件再notify,且没有触发虚假唤醒。这种情况下,线程1被唤醒时condition必然为true,所以不会暴露问题。但在生产环境中,多线程并发、虚假唤醒等情况都会导致代码逻辑失效。
正确的写法示例
符合cppreference规范的写法应该是:
#include <iostream> #include <atomic> #include <condition_variable> #include <mutex> #include <thread> std::mutex mu; std::atomic<bool> condition(false); std::condition_variable cv; void thread_1() { std::unique_lock<std::mutex> lock(mu); // 带谓词的wait,自动处理虚假唤醒 cv.wait(lock, []{ return condition.load(); }); } void thread_2() { std::unique_lock<std::mutex> lock(mu); condition.store(true); cv.notify_one(); } int main() { for (size_t i = 0; i < 100000; ++i) { std::thread t1(thread_1); std::thread t2(thread_2); t1.join(); t2.join(); } return 0; }
内容的提问来源于stack exchange,提问作者searchstar
相关产品推荐
相关产品推荐

