Я хотел бы проверить, если std::thread
закончил выполнение. В поисках stackoverflow я нашел следующее вопрос который решает эту проблему. В принятом ответе предлагается, чтобы рабочий поток установил переменную непосредственно перед выходом, а основной поток проверял эту переменную. Вот минимальный рабочий пример такого решения:
#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
из кеша своего процессора. Когда работник обновляет его, протокол когерентности кэша заботится о том, чтобы записать изменение работников в глобальную память и сделать недействительной кэш-память ЦП основного потока, чтобы он мог считывать обновленное значение из глобальной памяти. Разве весь смысл когерентности кэша в том, чтобы код, подобный приведенному выше, не работал?
Кто-то, кто прокомментировал принятый ответ, утверждает, что нельзя использовать простую переменную 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 не следует использовать в сочетании с многопоточностью — оно предназначено для работы с отображенным в память оборудованием.
Используя сырье bool
недостаточно
Выполнение программы содержит гонка данных если он содержит два конфликтующих действия в разных потоках, по крайней мере одно из которых не является атомарным, и ни одно из них не происходит раньше другого. Любая такая гонка данных приводит к неопределенному поведению. § 1.10 с21
Две оценки выражений конфликтуют, если одна из них изменяет ячейку памяти (1.7), а другая обращается или изменяет ту же ячейку памяти. § 1.10 р4
Ваша программа содержит гонку данных, в которой рабочий поток пишет в bool, а основной поток читает из него, но формального нет случается, перед тем связь между операциями.
Существует несколько способов избежать гонки данных, в том числе использование std::atomic<bool>
с соответствующим упорядочением памяти, использованием барьера памяти или заменой bool условной переменной.
Это не хорошо. Оптимизатор может оптимизировать
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
,
Алгоритмы когерентности кэша не везде присутствуют и не являются идеальными. Проблема, окружающая thread_finished
является то, что один поток пытается записать значение в него, в то время как другой поток пытается прочитать его. Это гонка данных, и если доступы не упорядочены, это приводит к неопределенному поведению.