std::condition_variable必须与std::unique_lock配合使用,因其wait()需原子地释放锁、挂起线程并唤醒后重锁,该语义仅对std::unique_lock定义;用其他锁类型将导致未定义行为或编译错误。

std::condition_variable 为什么必须和 std::unique_lock<:mutex> 配合使用
因为 std::condition_variable::wait() 内部会原子地释放锁并挂起线程,唤醒后自动重新加锁——这个“释放+等待+重锁”动作只对 std::unique_lock 有定义。用 std::lock_guard 编译直接报错,用裸 std::mutex 或 std::shared_lock 则行为未定义。
常见错误现象:wait() 返回后发现共享数据已被其他线程修改,或死锁在 wait();根本原因是没用 std::unique_lock,导致锁状态和条件变量不同步。
- 必须传
std::unique_lock<:mutex></:mutex>对象(不能是引用或指针) - 不要在
wait()前手动unlock():它自己会做 - 唤醒后需重新检查条件(用 while 而非 if),防止虚假唤醒
阻塞队列的 pop() 如何正确等待非空
核心是「先锁,再 while 检查,再 wait,再取值」。漏掉 while 循环或把条件判断写在 if 里,会导致线程在队列仍为空时继续执行,触发 front() 或 pop() 未定义行为。
void pop(T& item) {
std::unique_lock<std::mutex> lock(mtx_);
cond_not_empty_.wait(lock, [this] { return !queue_.empty(); });
item = std::move(queue_.front());
queue_.pop();
}
- 用
wait(lock, predicate)替代wait(lock)+ 手动 if,更安全简洁 - 移动语义要显式写
std::move,避免复制开销 - 别在 lambda 里调用
queue_.pop():wait 返回后才应操作队列
push() 通知消费者时该用 notify_one 还是 notify_all
单生产者单消费者场景下,notify_one() 足够且更高效;但多消费者时,如果用 notify_one() 而队列仍有数据,可能唤醒一个刚处理完的线程,而另一个正阻塞的线程被跳过,造成响应延迟甚至饥饿。
立即学习“C++免费学习笔记(深入)”;
- 多消费者 → 优先用
notify_all(),尤其当消费耗时不均或队列常驻多元素 - 多生产者 →
push()也要考虑是否需notify_all(),比如消费者可能因超时退出,需确保新数据不被遗漏 -
notify_one()不保证唤醒「等待最久」的线程,调度由系统决定
析构时如何安全终止所有 wait 线程
直接析构带活跃 wait() 的 std::condition_variable 会导致未定义行为——线程可能仍在访问已销毁的 mutex 或 lambda 捕获对象。
正确做法是设置停止标志 + 主动通知:
~BlockingQueue() {
{
std::unique_lock<std::mutex> lock(mtx_);
stop_ = true;
}
cond_not_empty_.notify_all();
cond_not_full_.notify_all();
}
- 停止标志
stop_必须是std::atomic_bool或加锁读写,否则存在竞态 - wait 的 predicate 要同时检查
!queue_.empty()和!stop_,例如:[this]{ return !queue_.empty() || stop_; } - 通知后,主线程应 join 所有工作线程,不能依赖线程自行退出
最容易被忽略的是 predicate 中对 stop_ 的检查 —— 如果只等队列非空,线程可能永远卡在 wait,无法响应析构指令。


















