You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

线程间条件变量通知耗时测量:代码正确性与负时长问题问询

条件变量通知耗时测量的问题分析

你的代码与疑问

你编写的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),不会出现负值,但这种情况下测量的是主线程内的逻辑耗时,完全不是跨线程的通知耗时,没有意义。

优化后的测量思路

要准确测量,需要确保:

  1. 等待线程先进入wait()状态,再触发通知操作。
  2. 尽可能贴近“通知发出”和“唤醒完成”的时刻记录时间点。

示例优化代码:

#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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 19:47:41