You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何修改条件变量的条件时必须持有互斥锁?

为什么修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 09:47:01