在设置条件变量之前,我是否必须锁定以检查与条件变量一起使用的工作可用标志,如果是这样,为什么?



我在线程池中有一个工作线程,其行为如下:

while (running)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_condition.wait(lock, [&] { return m_workAvailable; });
m_workAvailable = false;
lock.unlock();
// (Perform work...)
}

当我将工作放入这个工作线程时,它看起来像这样:

// (enqueue the actual work item...)
if (!m_workAvailable)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_workAvailable = true;
}
m_condition.notify_one();

在实践中看到这段代码的行为后,我猜测有某种与

相关的并发错误。
if(! m_workAvailable)

这里的check表示除非必要,否则绕过互斥锁的争用。但是,如果是这样的话,我实际上没有看到会导致这个问题的场景。

(:我已经尝试过m_workAvailableboolvolatile bool。我还没有尝试过std::atomic<bool>,尽管据我所知,这与volatile bool没有根本的不同。)

问题:

  1. 这确实是并发错误吗?
  2. 如果是这样,为什么——这允许出错吗?
  3. 如果是这样,有没有什么方法可以做我在这里做的事情,而不需要我每次都锁定互斥锁?

这确实是并发错误吗?

是的。

如果是,为什么——这允许什么出错?

这是未定义的行为。标准没有说明如果一个线程访问非原子类型,而另一个线程正在或可能正在修改它,会发生什么情况。你的代码就是这样做的。所以它可以以任何可以想象的方式破裂。

如果是这样的话,有没有一种方法可以做到我在这里做的事情,而不需要我在每次工作进入时都锁定互斥锁?

Yes, Do this:

{
std::lock_guard<std::mutex> lock(m_mutex);
m_workAvailable = true;
m_condition.notify_one();
}

您的平台,如果它有一个很好的实现,将检测互斥锁已经被持有,当您调用notify_one并避免再次同步。如果互斥锁没有被争用,其最终效果与在m_workAvailable集合期间不获取互斥锁大致相同。(如果它是争用的,无论如何成本是不可避免的。)

我还没有尝试过std::atomic<bool>,尽管据我所知,这与volatile bool在这里没有根本的不同。

嗯,嗯?当在一个线程中访问volatile bool,而另一个线程正在或可能正在修改它时,什么线程标准定义了该行为?除了微软的Visual Studio,我不知道其他的。相比之下,在这种情况下,std::atomic<bool>具有良好定义的语义。