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

std::condition_variable notify_one后wait获锁前行为及测试异常咨询

关于std::condition_variable唤醒不确定性的问题分析与解决

你遇到的这个随机性问题,核心是操作系统线程调度的不可预测性,再加上std::condition_variable的唤醒机制特性,咱们一步步拆解清楚:

为什么会出现两种不同的运行表现?

场景1:程序阻塞,只输出notify的前两行

这种情况里,notifyTask线程先抢到了mu互斥锁,执行完i=0和打印操作后调用cv.notify_one()——但这时候mainTask线程还没进入cv.wait(lk)的等待状态(要么还在排队抢mu锁,要么刚进入函数还没走到wait那一步)。这个notify信号相当于"白发"了,之后notifyTask抱着锁休眠5秒,结束后才释放锁。等mainTask终于拿到锁进入wait时,已经没有新的通知信号了,就一直阻塞下去。

场景2:休眠5秒后正常输出main task

这种场景下,mainTask线程先拿到mu锁,进入cv.wait(lk)——这里wait会自动释放锁,然后进入等待状态。之后notifyTask抢到锁,完成设置i、打印、调用cv.notify_one()的操作。这时候mainTask被唤醒,但它需要重新获取mu锁才能继续执行,而notifyTask还抱着锁在休眠,所以mainTask只能等5秒,等notifyTask释放锁后才能拿到锁,继续执行i++和打印,程序正常结束。

线程的调度顺序完全由操作系统内核决定,没有固定规律,这就是你看到两种不同表现的根本原因。

怎么修复这个问题?

要避免信号丢失和调度顺序带来的不确定性,给std::condition_variable搭配条件谓词是行业最佳实践,同时要确保通知线程先修改共享状态,再发送信号。

修改后的代码如下:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

class CondWait{
public:
    std::condition_variable cv;
    std::mutex mu;
    int i=0;
    // 新增条件标志,用来判断是否可以继续执行
    bool ready = false;
public:
    void mainTask(){
        std::unique_lock<std::mutex> lk(mu);
        // wait搭配谓词,既避免虚假唤醒,也解决信号丢失问题
        cv.wait(lk, [this](){ return ready; });
        i++;
        std::cout<<"main task, "<<i<<std::endl;
    }
    void notifyTask(){
        std::unique_lock<std::mutex> lk(mu);
        i = 0;
        ready = true; // 先设置条件标志
        std::cout<<"notify task, "<<i<<std::endl;
        cv.notify_one(); // 再发送通知
        std::cout<<"notify task, sleep 5 sec"<<std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }
};
int main() {
    CondWait condwait;
    std::thread t1(&CondWait::mainTask,&condwait);
    std::thread t2(&CondWait::notifyTask,&condwait);
    t1.join();
    t2.join();
    return 0;
}

修复逻辑说明:

  • 新增的ready标志作为共享的条件谓词,mainTask只有在ready为true时才会结束wait
  • 不管线程调度顺序如何:
    1. 如果mainTask先进入wait,它会释放锁等待ready变为true,当notifyTask设置ready=true并发通知后,mainTask会被唤醒,重新获取锁后检查谓词为真,继续执行
    2. 如果notifyTask先执行,它会先设置ready=true再发通知,之后mainTask拿到锁进入wait时,检查谓词已经为真,会直接跳过wait继续执行,不会阻塞

另外还有个小优化:如果notifyTask不需要在休眠时持有锁,可以在notify之后提前解锁,这样mainTask被唤醒后能立刻拿到锁执行,不用等5秒休眠结束:

void notifyTask(){
    std::unique_lock<std::mutex> lk(mu);
    i = 0;
    ready = true;
    std::cout<<"notify task, "<<i<<std::endl;
    cv.notify_one();
    lk.unlock(); // 提前解锁,让mainTask可以立刻获取锁执行
    std::cout<<"notify task, sleep 5 sec"<<std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

内容的提问来源于stack exchange,提问作者tingda zhuang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 10:57:56