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

移除task++的lock_guard为何导致std::condition_variable wait_for超时?

std::condition_variable 相关问题解答

问题描述

学习std::condition_variable时编写了测试代码,移除worker_thread2中task++的std::lock_guard后出现wait_for超时情况,有以下疑问:

  • 如果wait执行前线程2已完成task++并调用notify,是否会引发丢失唤醒问题?
  • 是否必须给task++和cv.notify_all()加锁才能解决该问题?但看到示例中是先解锁再调用notify_all,说是为了避免等待线程再次阻塞,这里有点困惑。
  • wait_for的具体执行流程是什么?收到通知后是唤醒加锁再检查条件吗?

测试代码

std::mutex m;
std::condition_variable cv;
int task = 0;
void worker_thread2()
{
    {
         // std::lock_guard<std::mutex> lk(m);
        task++;
    }
    cv.notify_all();
    LOG("notify_all");
}
int main()
{
    std::thread worker(worker_thread2);
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait_for(lk, std::chrono::milliseconds(5000), [&]{
            if (task == 1) {
                LOG("1111111");
                return true;
            } else {
                LOG("0000000");
                return false;
            }
        });
    }
    std::string m = "Back in main(), data = " + std::to_string(task);
    LOG(m);
    worker.join();
}

运行结果

D:\code\c++11\condition_variable\cmake-build-debug\condition_variable.exe
2023-6-8 20:37:15.208  00000002023-6-8 20:37:15.208  notify_all

2023-6-8 20:37:20.210  1111111
2023-6-8 20:37:20.212  Back in main(), data = 1

Process finished with exit code 0

问题解答

1. 丢失唤醒的问题

肯定会出现丢失唤醒的情况。

比如执行顺序是:主线程还没进入wait_for,worker线程先跑完task++并调用notify_all,之后主线程才开始等待。这时条件变量的通知已经发出去了,但主线程还没开始监听,这个通知就直接丢失了。主线程会一直等超时,到时间后再次检查条件,发现task已经是1,才退出等待——这就是你看到运行结果里隔了5秒才输出1111111的原因。

2. 加锁的必要性

  • 首先,task是多线程共享变量,task++不是原子操作,必须用锁保护,否则会出现数据竞争,属于未定义行为。
  • 其次,为了避免丢失唤醒,必须保证检查条件和进入等待是原子操作。主线程检查task状态时必须持有锁,这样worker线程就没法在主线程检查完条件、还没进入等待的间隙修改task并发通知——从根源上堵死丢失唤醒的窗口。

至于示例里先解锁再调用notify_all,是性能优化:如果持有锁的时候发通知,被唤醒的线程会立刻尝试抢锁,但锁还在当前线程手里,唤醒的线程会再次阻塞,直到当前线程解锁。先解锁再发通知,能让被唤醒的线程直接拿到锁,减少一次上下文切换。但要注意,解锁和发通知之间不能有其他修改共享变量的操作,否则可能出问题。

回到你的代码,必须给task++加上锁,这是硬性要求。

3. wait_for的执行流程

结合你代码里带谓词的版本,完整流程是:

  1. 传入的unique_lock必须是已加锁状态(你的代码满足这个前提)。
  2. 立刻调用谓词检查条件:
    • 如果谓词返回true,直接返回,锁保持加锁状态。
    • 如果返回false,进入等待:
      a. 自动解锁unique_lock。
      b. 阻塞线程,等待通知或者超时。
  3. 收到通知或超时后:
    a. 自动重新加锁unique_lock。
    b. 再次调用谓词检查条件:
    • 返回true则退出,锁保持加锁;返回false(比如超时或虚假唤醒)也退出,锁同样保持加锁。

一句话总结:加锁→检查条件→(不满足则)解锁等待→(唤醒/超时)重新加锁→再检查条件→退出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 22:53:14