在等待通知 std::condition_variable 期间执行“等待回调”

Posted

技术标签:

【中文标题】在等待通知 std::condition_variable 期间执行“等待回调”【英文标题】:Execute a "wait callback" during the wait for a std::condition_variable to be notified 【发布时间】:2015-11-05 07:59:28 【问题描述】:

我以这种方式使用 std::condition_variable :

void wait()

    std::unique_lock<std::mutex> lock(m_stateCompletedMutex);

    m_waitCondition.wait(lock, [this]()return (m_state == STATE_COMPLETED););

对此我很满意,但现在我想在“等待期间”执行一些代码(我不知道我是否可以这样说,但这就是想法)例如更新GUI 或增加等待计数器,或其他任何东西。

说我们正在使用 Qt,我尝试过这样的事情:

void wait()

    std::unique_lock<std::mutex> lock(m_stateCompletedMutex);

    while (m_state != STATE_COMPLETED)
    
        m_waitCondition.wait(lock);

        // Use this an example, it could be any code, executed in the waiting thread
        qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
    

这里的目标是在我们等待工作线程完成时保持 GUI 响应(至少对系统事件,而不是用户输入)。

我对这段代码有两个问题:

1/ 是这样做的好方法,还是有更好的方法在“等待时”执行代码

2/“等待代码”(在我的示例中调用 qApp->processEvents)多久执行一次?它依赖于系统吗?还是取决于当前的 CPU 负载或其他任何东西?或者我应该使用 m_waitCondition.wait_for 来确保最低频率调用?

关于第 2 点/,我已经测试过监控它(使用 std::chrono::high_resolution_clock),在我的应用程序中延迟似乎在 200 毫秒到 4000 毫秒之间,我认为这是一个很大的范围。

【问题讨论】:

对于“等待时”执行代码,您需要创建新线程或进程。设计可以有 10 个线程,其中 9 个在给定时刻等待。通常.wait() 仅在对应的.notify 调用后返回。 虚假唤醒实际上很少见。 “虚假唤醒实际上很少见”:我也是这么想的。但是我已经用经过时间计数器测试了一个输出到控制台(而不是我的示例中的 qApp->processEvents 调用),并且消息大约每 200 毫秒到 4000 毫秒被写入控制台。 也就是说,您的.wait() 每 200-4000 毫秒唤醒一次只是虚假的(没有.notify() 在其他线程中调用)? 在我的示例中,qApp->processEvents 每 200-4000 毫秒调用一次?没有任何 notify()。 最简单的解决方案不是使用条件变量,而是让你的代码在它完成它正在做的任何事情时发出一个信号。信号发射可以通过 API 作为模板类型用户提供的仿函数的调用公开。 【参考方案1】:

不,这不是正确的方法。您绝不能在 Qt(或任何事件驱动框架)的主 (GUI) 线程上等待或休眠。

GUI 线程必须接收某种事件,而不是等待。如果一切都是 Qt(即不是一些不能被 Qt-isms “污染”的外部代码),那么只需让另一个线程在完成时发出一个信号。

否则,您可以采取user1034749的方法,并将回调传递给计算函数。反过来,该回调可以设置一个标志,或者坚持 Qt 的事件驱动特性,发出一个信号,沿着你的 GUI 进行下一步(即显示计算结果)。

如果你打电话给ProcessEvents(),很有可能你做错了

【讨论】:

【参考方案2】:

1/ 是这样做的好方法,还是有更好的方法在“等待时”执行代码

对我来说,“好方法”是从程序的 GUI 部分隐藏计算架构的细节。例如界面可能是这样的:

extern void calc_something(Params, const std::function<void ()> &end_calc_callback);

end_calc_callback 可以在另一个(非 GUI)线程上下文中调用。

所以用法会是这样的

std::atomic<bool> result_readyfalse;
calc_something(Params(), [&result_ready]()  result_ready = true; );
//wait result_ready

2/“等待代码”(我的>示例中的 qApp->processEvents 调用)多久执行一次?它依赖于系统吗?

您可以为QCoreApplication::processEvents 设置最大工作时间,请参阅 Qt 文档,了解设置超时时间取决于您的代码和用户, 例如让你有这样的代码:

std::atomic<bool> result_readyfalse;
calc_something(Params(), [&result_ready]()  result_ready = true; );
ProgressWindow pw;
pw.show();
while (!result_ready)
  qApp->processEvents(QEventLoop::ExcludeUserInputEvents, timeout);

假设你在 ProgressWindow 中有计时器来绘制一些动画, 人类可以延迟响应屏幕上的某些内容,比如说 200 毫秒 (https://en.wikipedia.org/wiki/Mental_chronometry) 然后取决于什么动画 你在 ProgressWindow 中显示,用户反应你可以选择超时。

【讨论】:

谢谢,但关于第 2 点/,我知道 processEvents 可以使用超时参数调用。我的问题不是关于这段代码被调用多长时间,而是多久调用一次。我的意思是,两个调用之间的延迟是多少,即使它不是“processEvents”。就我而言,我看到每次调用之间有 200 毫秒到 4000 毫秒的延迟,即使该调用只是打印到控制台的消息(不是 processEvent 调用)。这种延迟是如何造成的? 多久叫什么? processEvents工作多长时间? 如何频繁,而不是多长时间。在我的情况下,我看到 200ms-4000ms 延迟 每次调用之间,即使调用只是打印到控制台的消息(不是 processEvent 调用)。这种延迟是如何造成的? 如果你使用绝对时钟来测量,而不是进程滴答测量,那么它取决于系统负载,加上要处理的事件数量。要解决此问题,您可以例如降低计算线程的优先级与 GUI 线程相比,这将有助于减少 processEvents 调用之间的延迟。

以上是关于在等待通知 std::condition_variable 期间执行“等待回调”的主要内容,如果未能解决你的问题,请参考以下文章

内置锁synchronized下的等待通知机制

在单个线程上发出通知会唤醒所有等待的线程

线程协作-等待与通知

在等待通知 std::condition_variable 期间执行“等待回调”

conditon 实现等待/通知

在 C/C++ 共享内存中等待和通知