condition_variable死锁原因分析及带谓词wait与普通wait的差异
condition_variable两种wait用法的差异分析
你编写的多线程代码期望按first-second-third顺序执行,第一种无标志位的实现出现死锁,而添加布尔标志位并使用带谓词的wait后程序正常执行,核心差异在于是否处理「虚假唤醒」以及「等待条件的正确性校验」,具体分析如下:
一、无谓词的wait(unique_lock)为什么会死锁?
先看你的第一段代码:
class Foo { public: void printfirst() { printf("first"); } void printsecond() { printf("second"); } void printthird() { printf("third"); } Foo() { } mutex mtx; condition_variable cv1, cv2; void first() { lock_guard<mutex> l(mtx); printfirst(); cv1.notify_one(); } void second() { unique_lock<mutex> ul(mtx); cv1.wait(ul); printsecond(); cv2.notify_one(); } void third() { unique_lock<mutex> ul(mtx); cv2.wait(ul); printthird(); } };
死锁的核心原因有两个:
- 唤醒时机不匹配:如果
first()线程先执行,它拿到锁打印first后调用cv1.notify_one(),但此时second()线程还没调用cv1.wait(ul)——唤醒信号「发早了」,second()后续进入等待时,会永远等不到唤醒,进而third()也会永远等待,最终整个程序死锁。 - 无法处理虚假唤醒:即使唤醒时机对上,
condition_variable的wait可能因为系统调度等原因被「虚假唤醒」(没有收到notify就被唤醒),此时second()会直接往下执行,但实际first()还没完成,破坏执行顺序,甚至引发其他问题。
二、带谓词的wait(unique_lock, Predicate)为什么能正常工作?
再看修正后的代码:
class Foo { public: void printfirst() { printf("first"); } void printsecond() { printf("second"); } void printthird() { printf("third"); } Foo() { firstready=false; secondready=false; } mutex mtx; bool firstready,secondready; condition_variable cv1, cv2; void first() { lock_guard<mutex> l(mtx); printfirst(); firstready=true; cv1.notify_one(); } void second() { unique_lock<mutex> ul(mtx); cv1.wait(ul,[&]{return firstready;}); printsecond(); secondready=true; cv2.notify_one(); } void third() { unique_lock<mutex> ul(mtx); cv2.wait(ul,[&]{return secondready;}); printthird(); } };
带谓词的wait本质是语法糖,等价于以下循环逻辑:
while (!predicate()) { wait(lock); }
它会先检查谓词是否为true,如果是就直接返回;如果不是,就释放锁进入等待。被唤醒后,会重新获取锁,再次检查谓词——只有当谓词为true时才会退出等待,否则继续等待。
它解决了两个核心问题:
- 处理唤醒时机不匹配:如果
first()先执行,设置firstready=true并唤醒cv1,后续second()进入wait时,会先检查firstready是否为true,发现已经满足条件,直接跳过等待执行后续逻辑,不会出现「等不到唤醒」的情况。 - 避免虚假唤醒:即使被虚假唤醒,谓词检查会发现条件不满足(比如
firstready还是false),会继续回到等待状态,保证只有当真正的条件满足时才会执行后续代码。
三、两种wait用法的核心差异总结
- 无谓词
wait(lock):仅响应唤醒信号,不校验等待条件。存在唤醒信号丢失、虚假唤醒的风险,几乎不会在生产代码中单独使用。 - 带谓词
wait(lock, pred):内置循环逻辑,每次被唤醒后都会重新检查谓词是否满足。既解决了唤醒时机不匹配导致的信号丢失问题,又能抵御虚假唤醒,是condition_variable的常规正确用法。
内容的提问来源于stack exchange,提问作者chenheng
相关产品推荐
相关产品推荐

