在等待通知 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 期间执行“等待回调”的主要内容,如果未能解决你的问题,请参考以下文章