【发布时间】:2021-07-03 19:58:14
【问题描述】:
如果数据竞争不是问题,我可以使用 std::condition_variable 来启动(即发信号)和停止(即等待)工作线程吗?
例如:
std::atomic<bool> quit = false;
std::atomic<bool> work = false;
std::mutex mtx;
std::condition_variable cv;
// if work, then do computation, otherwise wait on work (or quit) to become true
// thread reads: work, quit
void thread1()
{
while ( !quit )
{
// limiting the scope of the mutex
{
std::unique_lock<std::mutex> lck(mtx);
// I want here is to wait on this lambda
cv.wait(lck, []{ return work || quit; });
}
if ( work )
{
// work can become false again while working.
// I want here is to complete the work
// then wait on the next iteration.
ComputeWork();
}
}
}
// work controller
// thread writes: work, quit
void thread2()
{
if ( keyPress == '1' )
{
// is it OK not to use a mutex here?
work = false;
}
else if ( keyPress == '2' )
{
// ... or here?
work = true;
cv.notify_all();
}
else if ( keyPress == ESC )
{
// ... or here?
quit = true;
cv.notify_all();
}
}
更新/总结:由于 Adam 描述的“丢失唤醒”场景,不安全。
cv.wait(lck, predicate()); 可以等价写成while(!predicate()){ cv.wait(lck); }。
更容易看到问题:while(!predicate()){ /*lost wakeup can occur here*/ cv.wait(lck); }
可以通过将谓词变量的任何读/写操作放在互斥范围内来修复:
void thread2()
{
if ( keyPress == '1' )
{
std::unique_lock<std::mutex> lck(mtx);
work = false;
}
else if ( keyPress == '2' )
{
std::unique_lock<std::mutex> lck(mtx);
work = true;
cv.notify_all();
}
else if ( keyPress == ESC )
{
std::unique_lock<std::mutex> lck(mtx);
quit = true;
cv.notify_all();
}
}
【问题讨论】:
-
你应该没问题,因为读取原子会导致内存负载,因此每次条件变量检查其谓词时都会获取互斥锁。我确实遇到过一次,非常早在 MSVC 对 condition_variable 的支持中,它在读取条件变量检查的谓词内部的原子之前没有正确添加内存栅栏(并且错误的代码生成最终消失了)。
-
条件变量的全部意义在于以原子方式唤醒等待线程,避免任何形式的休眠。您的更新不是理想的解决方案。您最初是在正确的路线上,但有必要在更改标志后通知条件变量。
标签: c++ multithreading concurrency mutex condition-variable