C++11 condition_variable::wait()通知后谓词为真仍不返回原因
问题描述
我编写了如下测试代码,预期Thread-1会等待Thread-2发出通知、将条件置为true后执行结束,希望程序按顺序打印(Step 1)到(Step 5):
#include <atomic> #include <future> #include <chrono> #include <mutex> #include <condition_variable> #include <memory> #include <thread> #include <iostream> using namespace std; using namespace std::chrono; int main() { atomic_bool ab(false); mutex m; // lock object condition_variable c; auto f1 = async(launch::async, [&](){ unique_lock<mutex> _(m); cout << "(Step 1)\n"; c.wait(_, [&]{return (bool)ab;}); cout << "(Step 5)\n"; }); auto f2 = async(launch::async, [&](){ this_thread::sleep_for(1s); // wait for f1 starting unique_lock<mutex> _(m); cout << "(Step 2) signal\n"; c.notify_one(); cout << "(Step 3) \n"; }); this_thread::sleep_for(3s); // condition is waiting for "ab = true" cout << "(Step 4) \n"; ab = true; f1.wait(); f2.wait(); return 0; }
但程序实际运行仅输出如下内容:
(Step 1) (Step 2) signal (Step 3) (Step 4)
之后程序持续挂起,始终不会打印(Step 5),为何代码中的condition_variable::wait()调用没有正常返回?
原因分析
核心原因是条件变量使用逻辑错误,出现了无效唤醒和通知丢失两个问题,最终导致等待线程永久阻塞。
首先明确带谓词的condition_variable::wait()的固定执行逻辑:
- 线程持有传入的互斥锁进入wait函数
- 先检查谓词返回值:如果为
true,直接返回继续执行后续逻辑 - 如果谓词为
false,自动释放持有的互斥锁,将当前线程加入条件变量的等待队列,进入阻塞状态 - 被通知唤醒后,线程会先重新尝试获取互斥锁,拿到锁后再次执行谓词检查:谓词为
true就返回,为false则再次释放锁回到阻塞状态
对应代码的实际执行时序如下:
- f1线程率先启动,拿到互斥锁后打印(Step 1),检查到
ab为false,于是释放锁进入阻塞。 - 程序启动1秒后f2线程启动,此时f1已经释放锁,f2顺利拿到互斥锁,打印(Step 2)后调用
notify_one():这个通知确实能发送到阻塞中的f1,但此时互斥锁还被f2持有,f1无法立刻获取锁执行后续逻辑,只能进入等锁状态。 - f2打印完(Step 3)后lambda执行结束,持有的
unique_lock对象析构、自动释放互斥锁,f1终于拿到锁,紧接着执行谓词检查:此时距离程序启动才过了1秒多,主线程还处于3秒的sleep阶段,ab的值仍然是false,wait判定条件不满足,立刻再次释放锁、回到阻塞状态。f2发送的这个通知没有配合条件修改,属于完全无效的操作,没有任何实际作用。 - 距离程序启动3秒后,主线程sleep结束,打印(Step 4),将
ab设置为true——但修改完等待条件后,代码没有调用c.notify_one()唤醒阻塞在条件变量上的f1,f1会一直留在等待队列中挂起,永远不会醒来执行后续打印(Step 5)的逻辑。
除此之外代码还有两处不规范的点,在复杂场景下可能触发其他问题:
- 修改谓词依赖的共享变量
ab时,没有使用wait对应的互斥锁保护,虽然atomic_bool本身不会触发数据竞争,但会导致条件修改和等待线程的谓词检查之间的可见性无法和条件变量的唤醒逻辑正确同步。 - 通知的发送时机错误,在没有修改等待条件的前提下发送通知,属于无意义的冗余操作。
修正方案
条件变量的标准使用流程必须严格遵循以下规则:
- 等待方:持锁 -> 检查谓词,不满足则调用wait阻塞
- 通知方:持锁 -> 修改谓词依赖的共享变量 -> 释放锁 -> 发送通知(持锁状态下也可以发送通知,只是会触发一次额外的锁竞争,不影响逻辑正确性)
- 禁止在未修改等待条件时发送通知,也禁止修改完等待条件后不发送通知
修正后的核心代码片段如下:
auto f1 = async(launch::async, [&](){ unique_lock<mutex> _(m); cout << "(Step 1)\n"; c.wait(_, [&]{return (bool)ab;}); cout << "(Step 5)\n"; }); auto f2 = async(launch::async, [&](){ this_thread::sleep_for(1s); cout << "(Step 2)\n"; cout << "(Step 3)\n"; }); this_thread::sleep_for(3s); cout << "(Step 4)\n"; { lock_guard<mutex> _(m); // 修改共享条件时持锁 ab = true; } c.notify_one(); // 修改完条件后立刻发送通知唤醒等待线程 f1.wait(); f2.wait();
内容的提问来源于stack exchange,提问作者Troskyvs
相关产品推荐
相关产品推荐

