Возможен ли тупик в этом простом сценарии?

Пожалуйста, смотрите следующий код:

std::mutex mutex;
std::condition_variable cv;
std::atomic<bool> terminate;

// Worker thread routine
void work() {
while( !terminate ) {
{
std::unique_lock<std::mutex> lg{ mutex };
cv.wait(lg);

// Do something
}
// Do something
}
}

// This function is called from the main thread
void terminate_worker() {
terminate = true;
cv.notify_all();
worker_thread.join();
}

Может ли произойти следующий сценарий?

  1. Рабочий поток ждет сигналов.
  2. Основной поток называется terminate_worker();
    • Основной поток устанавливает атомную переменную terminate в true, а затем подал сигнал рабочему потоку.
    • Теперь рабочий поток просыпается, выполняет свою работу и загружается из terminate, На этом этапе изменение в terminate сделано по главной ветке пока не видно, поэтому рабочий поток решает дождаться другого сигнала.
  3. Теперь тупик возникает …

Интересно, это когда-нибудь возможно. Как я понял, std::atomic только гарантирует отсутствие гонки, но порядок памяти — это совсем другое. Вопросы:

  1. Это возможно?
  2. Если это невозможно, возможно ли это, если terminate не атомная переменная, а просто bool? Или атомарность тут не причем?
  3. Если это возможно, что мне делать?

Спасибо.

4

Решение

Я не верю, что то, что вы описываете, возможно, так как cv.notify_all() afaik (поправьте меня, если я ошибаюсь) синхронизируется с wait()поэтому, когда рабочий поток просыпается, он увидит изменение terminate,

Тем не мение:

Тупик может произойти следующим образом:

  1. Рабочий поток (WT) определяет, что terminate флаг все еще ложный.

  2. Основной поток (MT) устанавливает terminate флаг и звонки cv.notify_all(),

  3. Поскольку никто не ожидает переменную условия, уведомление становится «потерянным / проигнорированным».
  4. MT звонки join и блоки.
  5. WT идет спать ( cv.wait()) и блоки тоже.

Решение:

Хотя вам не нужно держать блокировку во время вызова cv.notify, вы

  • должен держать блокировку, пока вы модифицируете terminate (даже если это атомное)
  • должны убедиться, что проверка состояния и фактический вызов wait произойдет, когда вы держите тот же замок.

Вот почему существует форма wait который выполняет эту проверку непосредственно перед отправкой потока в спящий режим.

Исправленный код (с минимальными изменениями) может выглядеть так:

// Worker thread routine
void work() {
while( !terminate ) {
{
std::unique_lock<std::mutex> lg{ mutex };
if (!terminate) {
cv.wait(lg);
}

// Do something
}
// Do something
}
}

// This function is called from the main thread
void terminate_worker() {
{
std::lock_guard<std::mutex> lg(mutex);
terminate = true;
}
cv.notify_all();
worker_thread.join();
}
2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]