线程间条件变量通知耗时测量:代码正确性与负时长问题问询
条件变量通知耗时测量的问题分析
你的代码与疑问
你编写的C++代码如下:
bool ready{false}; mutex m; condition_variable cv; decltype(chrono::steady_clock::now()) t1; decltype(chrono::steady_clock::now()) t2; void notifying_thread() { { lock_guard<mutex> lg{m}; ready = true; } t1 = chrono::steady_clock::now(); cv.notify_one(); } void waiting_thread() { unique_lock<mutex> ul{m}; cv.wait(ul, [] { return ready; }); t2 = chrono::steady_clock::now(); } int main() { auto f1{async(notifying_thread)}; auto f2{async(waiting_thread)}; f1.get(); f2.get(); cout << chrono::duration_cast<chrono::microseconds>(t2 - t1).count() << " µs\n"; return 0; }
你提出的疑问:
- 实现是否正确?
chrono::steady_clock::now()的位置是否恰当?- 为何有时出现负值耗时?
async的启动策略是否有影响?
问题解析
1. 实现是否正确?
不正确。你当前测量的不是条件变量“从通知发出到等待线程被唤醒”的真实耗时,而是包含了大量额外开销:
- 系统线程调度的随机延迟
- 等待线程重新获取互斥锁的竞争时间
- 线程执行顺序不确定导致的无效测量(比如等待线程根本没进入等待状态就直接返回)
2. steady_clock::now()的位置是否恰当?
位置不恰当:
t1放在解锁互斥锁之后、notify_one()之前,此时通知线程可能被系统抢占,导致notify_one()的实际执行时间远晚于t1的记录时间,无法准确标记“通知发出”的时刻。t2放在wait()返回之后,但wait()返回前需要重新获取互斥锁,这个锁竞争的时间被计入了测量结果,无法反映真实的唤醒耗时。
3. 为何出现负值耗时?
核心原因是线程调度的不确定性:
当通知线程解锁互斥锁后,可能立即被系统调度挂起;此时等待线程抢占到CPU,快速完成互斥锁获取、wait()条件检查(此时ready已经为true)并记录t2;之后通知线程才被重新调度,执行t1的记录。这种情况下t2的时间早于t1,自然会出现负值。
另外,steady_clock是单调递增的,不存在时钟回退问题,负值完全由线程执行顺序颠倒导致。
4. async的启动策略是否有影响?
有直接影响。std::async的默认启动策略是std::launch::async | std::launch::deferred,实现可以选择:
- 异步启动线程:此时线程调度完全由系统决定,容易出现上述的执行顺序颠倒,导致负值。
- 延迟执行(
deferred):线程会在调用get()时才在主线程中执行,此时执行顺序是固定的(先执行notifying_thread,再执行waiting_thread),不会出现负值,但这种情况下测量的是主线程内的逻辑耗时,完全不是跨线程的通知耗时,没有意义。
优化后的测量思路
要准确测量,需要确保:
- 等待线程先进入
wait()状态,再触发通知操作。 - 尽可能贴近“通知发出”和“唤醒完成”的时刻记录时间点。
示例优化代码:
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <chrono> #include <future> using namespace std; bool ready{false}; bool waiting{false}; mutex m; condition_variable cv; condition_variable cv_wait_sync; decltype(chrono::steady_clock::now()) t1; decltype(chrono::steady_clock::now()) t2; void notifying_thread() { // 等待等待线程进入wait状态 unique_lock<mutex> ul{m}; cv_wait_sync.wait(ul, []{ return waiting; }); lock_guard<mutex> lg{m}; ready = true; t1 = chrono::steady_clock::now(); cv.notify_one(); } void waiting_thread() { unique_lock<mutex> ul{m}; waiting = true; cv_wait_sync.notify_one(); // 通知通知线程:已准备好等待 cv.wait(ul, [] { return ready; }); t2 = chrono::steady_clock::now(); } int main() { // 强制异步启动线程,避免延迟执行 auto f2 = async(launch::async, waiting_thread); auto f1 = async(launch::async, notifying_thread); f1.get(); f2.get(); cout << chrono::duration_cast<chrono::microseconds>(t2 - t1).count() << " µs\n"; return 0; }
这个版本通过额外的条件变量cv_wait_sync确保等待线程先进入等待状态,再触发通知,避免了无效测量和执行顺序颠倒的问题。
内容的提问来源于stack exchange,提问作者alex
相关产品推荐
相关产品推荐

