C++ condition_variable带谓词wait的异常行为及可靠性确认
C++ condition_variable带谓词的wait行为确认
我修改了C++ condition_variable的示例代码,尝试让变量在等待启动前被通知,最终得到一段完全不发送通知但所有线程都被唤醒的代码。发现几个有趣的现象:
- 仅调用
cv.wait(lk)会导致无限等待 - 带谓词的
cv.wait(lk, []{return i == 1;})在谓词满足时不会进入等待 cv.wait(lk, []{return true;})不等价于cv.wait(lk)
我需要确认这种行为是否属于预期情况,以及能否依赖该特性:比如调用cond_var.wait(lock, []() { return !packet_queue.empty(); })时,若队列非空,无需收到通知就不会进入等待。
测试代码
#include <iostream> #include <condition_variable> #include <thread> #include <chrono> std::condition_variable cv; std::mutex cv_m; // This mutex is used for three purposes: // 1) to synchronize accesses to i // 2) to synchronize accesses to std::cerr // 3) for the condition variable cv int i = 0; void waits(int id) { std::this_thread::sleep_for(std::chrono::seconds(2)); std::unique_lock<std::mutex> lk(cv_m); for (int t=10; t!=0; t--) { std::cerr << id << " waiting " << t << " seconds... \n"; std::this_thread::sleep_for(std::chrono::seconds(1)); } // cv.wait(lk); cv.wait(lk, []{return i == 1;}); for (int t=10; t!=0; t--) { std::cerr << id << " finish waiting " << t << " seconds... \n"; std::this_thread::sleep_for(std::chrono::seconds(1)); } } void signals() { // std::this_thread::sleep_for(std::chrono::seconds(1)); { std::lock_guard<std::mutex> lk(cv_m); std::cerr << "Notifying...\n"; } // cv.notify_all(); // std::this_thread::sleep_for(std::chrono::seconds(1)); { std::lock_guard<std::mutex> lk(cv_m); i = 1; std::cerr << "Notifying again...\n"; } // cv.notify_one(); } int main() { std::thread t1(waits, 0), t2(waits, 1), t3(waits, 2), t4(signals); t1.join(); t2.join(); t3.join(); t4.join(); }
行为解释与确认
这种行为完全符合C++标准的预期,可以放心依赖该特性:
- 带谓词的wait执行逻辑
根据C++标准,cv.wait(lk, pred)等价于以下代码:
while (!pred()) { cv.wait(lk); }
也就是说,调用带谓词的wait时,会首先检查谓词是否满足:
- 如果满足,直接返回,不会进入等待状态
- 如果不满足,才会释放锁并进入等待,直到收到通知后重新检查谓词
这就是测试代码中线程直接跳过等待的原因:当i=1已经被设置后,第一次检查谓词就满足条件,无需等待任何通知。
- 两种wait调用的核心区别
cv.wait(lk)会无条件释放锁并进入等待,必须收到通知(或遭遇虚假唤醒)才能被唤醒cv.wait(lk, []{return true;})会先检查谓词,因谓词永远为true,直接返回,不会进入等待状态。这和cv.wait(lk)的行为完全不同。
- 队列非空场景的可靠性
对于cond_var.wait(lock, []() { return !packet_queue.empty(); })这类场景,完全可以依赖该特性:调用wait时若队列已非空,谓词返回true,wait直接返回,无需等待通知。这是条件变量的标准正确用法,既规避了虚假唤醒问题,也处理了“条件在wait前已满足”的场景。
内容的提问来源于stack exchange,提问作者VojtaK
相关产品推荐
相关产品推荐

