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

Why do we need an empty std::lock_guard before doing condition variable notify?

本文关键字:一个 std guard lock 变量 条件 执行 通知 我们 为什么      更新时间:2023-10-16

我目前正在研究谷歌的灯丝工作系统。您可以在此处找到源代码。让我感到困惑的部分是这个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();
}

我很困惑为什么我们需要锁定和解锁,即使锁定和解锁之间没有操作。是否有任何情况需要这种空锁和解锁?

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

mExitRequested.store(true);
mLooperCondition.notify_all();

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

比赛将是:

  1. 其他线程检查mExitRequestedfalse
  2. 我们将mExitRequested设置为true.
  3. 我们称mLooperCondition.notify_all.
  4. 其他线程等待mLooperCondition
  5. 哎呀。等待已经发生的通知。

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

所以,要让这场比赛发生,它必须像这样发生:

  1. 其他线程获取mLooperLock
  2. 其他线程检查mExitRequested,它false
  3. 我们将mExitRequested设置为true
  4. 我们称mLooperCondition.notify_all.
  5. 其他线程等待mLooperCondition,释放mLooperLock
  6. 哎呀。等待已经发生的通知。

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

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

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

再试一次:

  1. 其他线程获取mLooperLock
  2. 其他线程检查mExitRequestedfalse
  3. 我们将mExitRequested设置为true
  4. 通过获取和释放nLooperLock,在另一个线程释放mLooperLock之前,我们不会有任何向前的进展。
  5. 我们称mLooperCondition.notify_all.

现在,要么其他线程在该条件下阻塞,要么不阻塞。如果没有,那就没有问题了。如果是这样,仍然没有问题,因为mLooperLock的解锁是条件变量的原子"解锁和等待"操作,保证它看到我们的通知。

相关文章: