std::condition_variable必须与std::mutex配合使用,因其自身无线程安全性,所有共享状态访问须由互斥锁保护;需用while循环检查条件防虚假唤醒,notify端也须在锁内修改状态并通知。

std::condition_variable 为什么必须和 std::mutex 一起用
因为 std::condition_variable 本身不提供线程安全,它只负责“等待”和“唤醒”,所有对共享状态的检查和修改都得由外部互斥锁保护。漏掉这一步,大概率触发未定义行为——比如虚假唤醒后读到脏数据,或者 wait() 前状态已改变却没被通知。
典型错误写法:
// ❌ 错误:没加锁就检查条件
if (ready == false) {
cv.wait(lock); // lock 可能根本没锁住 ready 变量
}
正确姿势是:先锁住共享变量 → 检查条件 → 条件不满足才 wait(此时 lock 会自动释放)→ 被唤醒后 lock 自动重新获取 → 再次检查条件(必须用 while,不是 if)。
- 必须用
while (condition == false)包裹wait(),不能用if:防止虚假唤醒(spurious wakeup)导致跳过检查 -
lock必须是std::unique_lock<:mutex></:mutex>,不能是std::lock_guard:因为wait()需要能临时释放并重新获取锁 - 修改共享状态的一方(notify 端),也必须在同一个 mutex 保护下修改变量,再调用
notify_one()或notify_all()
notify_one 和 notify_all 到底该选哪个
看你的等待者是不是“可互换”的。如果多个线程在等同一个逻辑事件(比如“缓冲区有新数据”),且只要一个线程处理即可,用 notify_one() 更高效;如果每个等待线程都需要响应(比如“所有 worker 停止工作”),必须用 notify_all()。
常见误判点:
- 以为 notify_one() 更“安全”就无脑用:结果是其他本该被唤醒的线程永远卡在 wait 中
- 在循环中反复 notify_one() 但没配对足够的 waiters:可能唤醒不存在的线程,浪费但不报错;更危险的是漏唤醒
-
notify_one()不保证唤醒“最早等待的那个”,只是唤醒一个当前阻塞的线程(具体哪个由调度器决定) -
notify_all()会唤醒所有在 wait 的线程,但它们仍需竞争 mutex,所以不会同时执行临界区 —— 这不是并发执行的信号,只是“都获得一次抢锁机会” - 如果 notify 发生在 wait 之前(即先改状态再 wait),
notify会丢失;必须靠 while + mutex + 条件变量三者配合来规避
一个最小可运行的 producer-consumer 示例
下面这段代码跑起来就能验证基本流程,重点看加锁顺序、条件检查方式和 notify 时机:
立即学习“C++免费学习笔记(深入)”;
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <iostream>
<p>std::queue<int> data_queue;
std::mutex mtx;
std::condition_variable cv;
bool ready = false;</p><p>void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待 ready == true
std::cout << "consumed: " << data_queue.front() << "\n";
data_queue.pop();
ready = false;
}</p><p>void producer() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::unique_lock<std::mutex> lock(mtx);
data_queue.push(42);
ready = true;
cv.notify_one(); // 通知 consumer
}</p><p>int main() {
std::thread t1(consumer);
std::thread t2(producer);
t1.join(); t2.join();
}注意:这里用 ready 是为了简化,实际中建议把条件封装成函数对象或 lambda,避免全局变量污染;生产环境还要考虑队列为空/满、异常安全、stop token 等问题。
容易被忽略的生命周期和异常安全问题
std::condition_variable 对象本身必须活到所有 wait 调用返回之后。如果它析构了还有线程在 wait,程序直接崩溃(SIGABRT 或 undefined behavior)。
- 别在线程函数里局部定义
std::condition_variable并传给其他线程 wait —— 它可能在 wait 返回前就被销毁 - 如果 wait 可能被中断(比如线程被
std::jthread请求 stop),记得捕获std::thread_interrupted或检查std::stop_token,否则异常会绕过 mutex 自动解锁机制 -
wait_for()和wait_until()返回值是std::cv_status,不是布尔值:要显式判断是否超时,不能只看返回值真假 - Windows 上 MinGW 或旧版 MSVC 可能对
wait_for的时钟精度支持较差,实测延迟可能远大于指定值
最稳妥的做法是把 std::condition_variable、std::mutex 和受保护的数据一起封装进类,用 RAII 管理生命周期 —— 否则光靠“记得别提前析构”这种约定,早晚出事。


















