Подтвердить что ты не робот

Нужно ли использовать std:: atomic, чтобы сигнализировать о завершении выполнения потока?

Я хотел бы проверить, завершено ли выполнение std::thread. Поиск stackoverflow я нашел следующий question, который решает эту проблему. В принятом ответе предлагается, чтобы рабочий поток установил переменную прямо перед выходом и основной поток проверил эту переменную. Вот минимальный рабочий пример такого решения:

#include <unistd.h>
#include <thread>

void work( bool* signal_finished ) {
  sleep( 5 );
  *signal_finished = true;
}

int main()
{
  bool thread_finished = false;
  std::thread worker(work, &thread_finished);

  while ( !thread_finished ) {
    // do some own work until the thread has finished ...
  }

  worker.join();
}

Кто-то, кто прокомментировал принятый ответ, утверждает, что в качестве сигнала нельзя использовать простую переменную bool, код был сломан без барьера памяти и использование std::atomic<bool> было бы правильным. Моя первоначальная догадка заключается в том, что это неправильно, и простого bool достаточно, но я хочу убедиться, что я чего-то не упускаю. Требуется ли для вышеуказанного кода std::atomic<bool> правильно?

Предположим, что основной поток и рабочий выполняются на разных ЦП в разных сокетах. Я думаю, что произойдет, что основной поток читает thread_finished из своего кэша процессора. Когда работник обновляет его, протокол когерентности кэша берет на себя обязательство написать рабочие изменения в глобальную память и аннулировать кеш процессора основного потока, чтобы он считывал обновленное значение из глобальной памяти. Разве не все указывает на то, что когерентность кеша делает код, подобный приведенному выше, просто работает?

4b9b3361

Ответ 1

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

Комментарий правильный: простой bool недостаточен, потому что неатомные записи из потока, который устанавливает от thread_finished до true, могут быть переупорядочены.

Рассмотрим поток, который устанавливает статическую переменную x на некоторое очень важное число, а затем сигнализирует о ее выходе, например:

x = 42;
thread_finished = true;

Когда ваш основной поток видит thread_finished, установленный в true, он предполагает, что рабочий поток завершен. Однако, когда ваш основной поток проверяет x, он может установить, что он настроен на неправильное число, потому что две записи выше были переупорядочены.

Конечно, это всего лишь упрощенный пример, иллюстрирующий общую проблему. Использование std::atomic для вашей переменной thread_finished добавляет барьер памяти, гарантируя, что все записи будут выполнены до его завершения. Это фиксирует потенциальную проблему записи вне порядка.

Другая проблема заключается в том, что чтение энергонезависимых переменных может быть оптимизировано поэтому основной поток никогда не заметит изменения в значке thread_finished.


Важное замечание: сделать ваш thread_finished volatile не, чтобы исправить проблему; на самом деле, volatile не следует использовать в сочетании с потоковой обработкой - он предназначен для работы с аппаратным обеспечением, отображенным в памяти.

Ответ 2

Использование raw bool недостаточно.

Выполнение программы содержит гонку данных, если она содержит два конфликтующих действия в разных потоках, по крайней мере один из которых не является атомарным, и не происходит до другого. Любая такая гонка данных приводит к поведению undefined. § 1.10 p21

Две оценки выражений противоречат друг другу, если один из них изменяет расположение памяти (1.7), а другой получает или изменяет одну и ту же ячейку памяти. § 1.10 p4

Ваша программа содержит гонку данных, в которой рабочий поток записывает в bool, и основной поток читает от него, но формальных событий не существует - до отношения между операциями.

Существует множество способов избежать гонки данных, включая использование std::atomic<bool> с соответствующими порядками памяти, использование барьера памяти или замену bool на переменную условия.

Ответ 3

Это не нормально. Оптимизатор может оптимизировать

  while ( !thread_finished ) {
    // do some own work until the thread has finished ...
  }

в

  if(!thread_finished)
    while (1) {
      // do some own work until the thread has finished ...
    }

предполагая, что он может доказать, что "некоторая собственная работа" не изменяется thread_finished.

Ответ 4

Алгоритмы когерентности кэша отсутствуют повсюду и не идеальны. Проблема, связанная с thread_finished, заключается в том, что один поток пытается записать значение для нее, а другой поток пытается ее прочитать. Это гонка данных, и если обращения не упорядочены, это приводит к поведению undefined.