为什么在执行条件变量通知之前我们需要一个空的std :: lock_guard?

kev*_*nyu 8 c++ concurrency mutex condition-variable

我目前正在研究Google的细丝工作系统。您可以在此处找到源代码。令我困惑的部分是这个requestExit()方法:

void JobSystem::requestExit() noexcept {
    mExitRequested.store(true);

    { std::lock_guard<Mutex> lock(mLooperLock); }
    mLooperCondition.notify_all();

    { std::lock_guard<Mutex> lock(mWaiterLock); }
    mWaiterCondition.notify_all();
}
Run Code Online (Sandbox Code Playgroud)

我很困惑,为什么即使在锁定和解锁之间没有任何动作,也需要锁定和解锁。在任何情况下都需要这种空的锁定和解锁吗?

Dav*_*rtz 8

这有点hack。首先,让我们看一下没有代码的代码:

mExitRequested.store(true);
mLooperCondition.notify_all();
Run Code Online (Sandbox Code Playgroud)

这里可能存在比赛条件。其他一些代码可能已经注意到这mExitRequested是错误的,并mLooperCondition在我们调用之后立即开始等待notify_all

比赛将是:

  1. 其他线程检查mExitRequestedfalse
  2. 我们设置mExitRequestedtrue
  3. 我们打电话mLooperCondition.notify_all
  4. 其他线程等待mLooperCondition
  5. 哎呀。等待通知已发生。

但是,为了等待条件变量,您必须保留关联的互斥体。因此,只有在其他线程持有mLooperLock互斥锁的情况下,才会发生这种情况。实际上,第4步实际上是:“其他线程释放mLooperLock并等待mLooperCondition

因此,要使这场比赛发生,它必须完全像这样发生:

  1. 其他线程获取mLooperLock
  2. 其他线程检查mExitRequestedfalse
  3. 我们设置mExitRequestedtrue
  4. 我们打电话mLooperCondition.notify_all
  5. 其他线程等待mLooperCondition释放mLooperLock
  6. 哎呀。等待通知已发生。

因此,如果我们将代码更改为:

mExitRequested.store(true);
{ std::lock_guard<Mutex> lock(mLooperLock); }
mLooperCondition.notify_all();
Run Code Online (Sandbox Code Playgroud)

这样可以确保没有其他线程可以检查mExitRequested并查看false然后等待mLooperCondition。因为另一个线程必须mLooperLock在整个过程中都持有该锁,所以这是不可能的,因为我们是在该过程的中间获得它的。

再试一次:

  1. 其他线程获取mLooperLock
  2. 其他线程检查mExitRequestedfalse
  3. 我们设置mExitRequestedtrue
  4. 通过获取和释放nLooperLock,在其他线程释放之前,我们不会取得任何进展mLooperLock
  5. 我们打电话mLooperCondition.notify_all

现在,其他线程在该条件上阻塞,或者没有。如果没有,那就没有问题。如果确实如此,则仍然没有问题,因为的解锁mLooperLock是条件变量的原子“解锁并等待”操作,从而保证了它看到我们的通知。