Должен ли я использовать атомный <bool> для переменной "exit" bool?

Мне нужно установить флаг для выхода другого потока. Этот другой поток проверяет флаг выхода время от времени. Должен ли я использовать атомный для флага, или достаточно простого bool и почему (с примером того, что может пойти не так, если я использую простой bool)?

#include <future>
bool exit = false;
void thread_fn()
{
    while(!exit)
    {
        //do stuff
        if(exit) break;
        //do stuff
    }
}
int main()
{
    auto f = std::async(std::launch::async, thread_fn);
    //do stuff
    exit = true;
    f.get();
}

Ответ 1

Должен ли я использовать атомный для переменной "exit" bool?

Да.

Используйте либо atomic<bool>, либо используйте ручную синхронизацию через (например) std::mutex. Ваша программа в настоящее время содержит гонку данных, причем один поток потенциально читает переменную, а другой поток записывает ее. Это Undefined Поведение.

В абзаце 1.10/21 стандарта С++ 11:

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

Определение "противоречия" приведено в пункте 1.10/4:

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

Ответ 2

Да, вы должны иметь некоторую синхронизацию. Самый простой способ, как вы говорите, - atomic<bool>.

Формально, как говорит @AndyProwl, в определении языка говорится, что использование атома здесь не дает поведения undefined. Для этого есть веские причины.

Во-первых, чтение или запись переменной может быть прервано на полпути с помощью переключателя потока; другой поток может видеть частично записанное значение, или если он изменяет значение, исходный поток будет видеть смешанное значение. Во-вторых, когда два потока работают на разных ядрах, у них есть отдельные кэши; запись значения хранит его в кеше, но не обновляет другие кеши, поэтому поток может не видеть значение, написанное другим потоком. В-третьих, компилятор может реорганизовать код на основе того, что он видит; в примере кода, если ничто внутри цикла не изменит значение exit, у компилятора нет причин подозревать, что значение изменится; он может превратить цикл в while(1).

Atomics адресует все три из этих проблем.

Ответ 3

на самом деле, в этом конкретном примере ничего не происходит с простым bool. единственным уведомлением является объявление переменной bool exit как изменчивой, чтобы сохранить ее в памяти. как архитектуры CISC, так и RISC реализуют bool read/write как строго атомную инструкцию процессора. также современные многоядерные процессоры имеют расширенный интеллектуальный кэш-модуль. поэтому никаких барьеров памяти не требуется. стандартная цитата не подходит для данного конкретного случая, потому что она имеет дело с единственной записью и чтением только из одного потока.