Зачем нам нужен пустой std :: lock_guard перед тем, как делать условную переменную notify? - PullRequest
8 голосов
/ 10 июня 2019

В настоящее время я изучаю систему вакансий Google Filament. Вы можете найти исходный код здесь . Меня смущает метод 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();
}

Я запутался, почему нам нужно блокировать и разблокировать, даже если между блокировкой и разблокировкой нет никаких действий. Есть ли случаи, когда необходима пустая блокировка и разблокировка?

1 Ответ

8 голосов
/ 10 июня 2019

Это что-то вроде хака.Во-первых, давайте посмотрим на код без этого:

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

Здесь возможны условия гонки.Какой-то другой код мог заметить, что mExitRequested был ложным и начал ждать mLooperCondition сразу после того, как мы вызвали notify_all.

Гонка будет:

  1. Проверка других потоковmExitRequested, это false.
  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. Другие проверки потоков mExitRequested, это false.
  3. Мы устанавливаем mExitRequested в true.
  4. Приобретая и выпуская nLooperLock, мыне сделать любой прогресс вперед, пока другой поток не освободит mLooperLock.
  5. Мы вызываем mLooperCondition.notify_all.

Теперь либо другой поток блокирует условие, либо нет.Если это не так, нет проблем.Если это так, проблемы все равно нет, поскольку разблокировка mLooperLock - это атомарная операция условной переменной «разблокировать и ждать», гарантирующая, что она увидит наше уведомление.

...