为何std::condition_variable在std::this_thread::sleep_for置于unique_lock域内时失效?
问题背景
取消注释producer()中位于unique_lock作用域内的sleep调用后,代码无法按预期工作——没有任何消息输出;但将该sleep移出锁的作用域,代码就能正常每秒生成并打印消息。
预期与异常现象
- 预期行为:
producer()每秒生成一条新消息存入data,consumer()实时打印该消息 - 异常现象:取消注释锁内
sleep后,程序无任何输出,完全不执行消息打印逻辑
原代码
#include <atomic> #include <condition_variable> #include <iostream> #include <mutex> #include <ratio> #include <string> #include <thread> #include <queue> std::string data; std::atomic<bool> ready; std::condition_variable cv; std::mutex m; void producer() { int c{}; while (true) { ++c; { std::unique_lock<std::mutex> lock{ m }; data = "count: " + std::to_string(c); // std::this_thread::sleep_for(std::chrono::seconds{ 1 }); // THIS LINE ready = true; } cv.notify_one(); } } void consumer() { while (true) { std::unique_lock<std::mutex> lock{ m }; cv.wait(lock, []{ return ready == true; }); std::cout << data << '\n'; ready = false; } } int main() { std::thread tp{ producer }; std::thread tc{ consumer }; tp.join(); tc.join(); }
核心原因分析
1. 锁被长时间持有,consumer无法获取锁执行条件检查
当sleep放在unique_lock的作用域内时,producer线程会持续持有互斥锁m达1秒。此时consumer线程在执行cv.wait(lock, ...)时,第一步就需要获取互斥锁m,但锁被producer死死占用,导致consumer根本无法进入条件变量的等待逻辑,更没法检查ready状态。
2. 通知时机与线程执行节奏不匹配,出现通知丢失
producer每次循环都会在释放锁后调用cv.notify_one(),但此时consumer还在等待获取锁,无法响应这个通知。等producer的sleep结束、释放锁后,consumer终于拿到锁,这时候producer已经进入下一轮循环,又重新获取了锁并开始sleep——consumer检查ready时,可能刚把上一轮的ready设为false,而新的ready还没被设置,条件不满足,consumer再次进入等待,但此时producer的notify_one()已经发过了,导致通知丢失,consumer永远等不到有效的唤醒信号。
3. 移出锁作用域后正常的逻辑
把sleep放到unique_lock代码块外面,意味着producer更新完data和ready后会立即释放锁。此时consumer可以马上获取锁,检查到ready为true,打印消息并将ready设为false。之后producer才开始sleep,这时候锁处于释放状态,consumer可以正常进入cv.wait()等待下一次通知,两者的同步节奏完全匹配,逻辑通顺。
内容的提问来源于stack exchange,提问作者Jasper1378

