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

См. следующий код:

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. The main thread called terminate_worker();
    • The main thread set the atomic variable terminate to true, and then signaled to the worker thread.
    • Теперь рабочий поток просыпается, выполняет свою работу и загружается из terminate. На этом этапе изменение terminate сделанное основным потоком еще не замечено, поэтому рабочий поток решает дождаться другого сигнала.
  3. Теперь возникает тупик...

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

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

Спасибо.


person Junekey Jeon    schedule 27.12.2016    source источник
comment
std::memory_order   -  person melak47    schedule 27.12.2016
comment
@ melak47 Пожалуйста, предоставьте более подробную информацию. Я знаю, что это о std::memory_order. Но я понятия не имею, как std::condition_variable действует с ограничениями порядка памяти.   -  person Junekey Jeon    schedule 27.12.2016
comment
Связано: stackoverflow. ком/вопросы/8819095/   -  person Leon    schedule 27.12.2016
comment
@н.м. что уже сделано. ОП спрашивает об упорядочении/синхронизации между атомарным хранилищем (с memor_order_seq_cst) и condition_variable::notify_all.   -  person Zeta    schedule 27.12.2016
comment
Что мне делать, правильно использовать условную переменную. Условие должно быть защищено мьютексом, атомарности недостаточно.   -  person n. 1.8e9-where's-my-share m.    schedule 27.12.2016
comment
@Zeta да, только что заметил это. Нужно больше кофе по утрам.   -  person n. 1.8e9-where's-my-share m.    schedule 27.12.2016
comment
Прочитайте обсуждение здесь stackoverflow.com/questions/17101922/.   -  person n. 1.8e9-where's-my-share m.    schedule 27.12.2016
comment
Этот код работает по назначению, когдаterminate не является атомарным. Делать его атомарным избыточно и расточительно.   -  person Pete Becker    schedule 27.12.2016


Ответы (1)


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

Однако:

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

  1. Рабочий поток (WT) определяет, что флаг terminate по-прежнему ложен.

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

  3. Поскольку в настоящее время никто не ждет переменную условия, уведомление становится «потерянным/игнорируемым».
  4. МТ вызывает 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();
}
person MikeMB    schedule 27.12.2016
comment
В любом случае OP должен после ожидания проверить состояние переменной завершения. В предоставленном коде мы не можем понять, почему переменная условия была уведомлена. это может быть либо терминальный запрос, либо обычная синхронизация с другим потоком. - person paweldac; 27.12.2016
comment
@paweldac: Да, он, вероятно, должен (без знания бизнес-логики трудно сказать), но какое это имеет отношение к вопросу, может ли возникнуть взаимоблокировка или нет? - person MikeMB; 27.12.2016
comment
Большое тебе спасибо. Я обнаружил, что в ссылке [en.cppreference.com/w/cpp/thread/ condition_variable] описано, что даже если общая переменная является атомарной, она должна быть изменена в мьютексе, чтобы правильно опубликовать изменение в ожидающем потоке. Это о том, что вы только что описали? Или это другая история? И еще, бессмысленно ли делать terminate атомарным? то есть я могу использовать только bool, не так ли? - person Junekey Jeon; 28.12.2016