std::condition_variable::wait 必须配 std::unique_lock,因其内部需自动释放并重获锁,而该机制仅对 std::unique_lock 有效;用 lock_guard 或裸 mutex 会导致编译失败或未定义行为。

std::condition_variable::wait 为什么必须配 std::unique_lock?
因为 wait() 内部会先释放锁、挂起线程,等被唤醒后再重新加锁——这个“自动释放+重获”逻辑只对 std::unique_lock<:mutex></:mutex> 有效。用 std::lock_guard 或裸 std::mutex::lock() 直接传给 wait() 会导致编译失败(类型不匹配)或运行时未定义行为。
常见错误现象:error: no matching member function for call to 'wait',或程序卡死/崩溃。
- 必须用
std::unique_lock构造时传入已 lock 的 mutex,或让它自己 lock -
wait()的 lambda 谓词不是可选的“优化”,而是防止虚假唤醒的必要手段:检查条件是否真满足,不满足就继续 wait - 不要在
wait()外层再手动 unlock —— 这会破坏原子性,导致竞争
notify_one() 和 notify_all() 到底该选哪个?
绝大多数生产者-消费者场景下,notify_one() 就够了,且更高效。只有当多个消费者可能因不同条件阻塞(比如有多个条件变量共用同一 mutex),或你明确需要“广播唤醒所有等待者”时,才用 notify_all()。
容易踩的坑:
立即学习“C++免费学习笔记(深入)”;
- 在持有 mutex 期间调用
notify_one()没问题,但没必要;它本身是无锁操作,放在 unlock 后更清晰(避免锁粒度扩大) - 只 notify 却没改共享状态(比如忘了
queue.push()或queue.pop()),消费者会被唤醒但条件仍不满足,立刻又回到 wait —— 看似“活锁” - notify 调用时机错位:比如生产者 push 后没 notify,或消费者 pop 后误 notify,都会导致死锁
缓冲区满/空时的 wait 条件怎么写才安全?
核心原则:所有对共享队列的访问(push、pop、empty()、size())必须严格包裹在同一个 std::unique_lock 保护下,且 wait 的谓词要复用同一把锁保护的状态判断。
典型错误写法:if (queue.empty()) { cond_var.wait(...); } —— 这里 empty() 和 wait() 之间存在竞态窗口。
- 正确写法是直接在
wait()中写谓词:cond_var.wait(lock, [&] { return !queue.empty(); }); - 生产者端同理:
cond_var.wait(lock, [&] { return queue.size() - 注意
queue.size()在某些 STL 实现中不是 O(1),若容量检查频繁,建议用独立计数器int count配合 mutex 维护
为什么 notify 之后要 unlock,而不是反过来?
顺序错了就会引发性能瓶颈甚至死锁。如果先 unlock 再 notify,被唤醒的线程会在 wait() 返回前尝试重新获取 mutex —— 此时若生产者还没 unlock,消费者就得等;而如果 notify 放在 unlock 之后,操作系统能更快调度消费者去争锁,减少上下文切换延迟。
实操建议:
- 标准模式是:修改共享状态 →
unlock()→notify_one() - 绝对不要
notify()→unlock():虽然不会 crash,但唤醒线程会立即阻塞在 mutex 上,失去通知意义 - 也不要用
lock()→ 修改 →notify()→unlock():锁住太久,降低并发度
整个流程里最易被忽略的,是谓词里对共享状态的读取必须与 wait 所处的锁完全一致——少一个括号捕获、多一个外部变量引用,都可能绕过锁保护,让条件检查失效。


















