Есть ли неявный барьер памяти с отношениями synchronized-with в thread :: join?

У меня есть работающий код, который запускает несколько потоков, выполняющих некоторые операции, и если какой-либо из них терпит неудачу, они устанавливают для общей переменной значение false.

Затем основной поток присоединяется ко всем рабочим потокам. Моделирование этого выглядит примерно так (я закомментировал возможное исправление, но не знаю, нужно ли оно):

#include <thread>
#include <atomic>
#include <vector>
#include <iostream>
#include <cassert>

using namespace std;

//atomic_bool success = true;
bool success = true;

int main()
{
    vector<thread> v;
    for (int i = 0; i < 10; ++i)
    {
        v.emplace_back([=]
        {
            if (i == 5 || i == 6)
            {
                //success.store(false, memory_order_release);
                success = false;
            }
        });
    }
    for (auto& t : v)
        t.join();

    //assert(success.load(memory_order_acquire) == false);
    assert(success == false);

    cout << "Finished" << endl;
    cin.get();
    return 0;
}

Есть ли вероятность, что основной поток прочитает переменную успеха как true, даже если один из рабочих установил ее в false?

Я обнаружил, что thread :: join () - это полный барьер памяти (source), но подразумевает ли это отношение synchronized-with со следующим чтением успеха переменная из основного потока, чтобы гарантировать получение новейшего значения?

Является ли исправление, которое я опубликовал (в закомментированном коде), необходимым в этом случае (или, может быть, другое исправление, если оно неверно)?

Есть ли вероятность того, что чтение переменной success будет оптимизировано (поскольку она не является изменчивой), и мы получим старое значение независимо от предположительно существующего неявного барьера памяти на thread :: join?

Предполагается, что код работает на нескольких архитектурах (не могу запомнить их все, у меня нет файла makefile), но есть как минимум x86, amd64, itanium, arm7.

Спасибо за любую помощь с этим.

Изменить: я изменил пример, потому что в реальной ситуации несколько потоков могут попытаться записать в переменную success.


person Mikaka    schedule 11.02.2017    source источник
comment
Разве источник не ясен об этом? Поскольку создание и структура потока определяется как операция синхронизации, присоединение к потоку обязательно происходит после его завершения. Таким образом, вы увидите все, что обязательно произошло до завершения потока.   -  person Jean-Baptiste Yunès    schedule 11.02.2017
comment
Я не уверен, поскольку, как вы можете видеть здесь, en.cppreference.com/w/cpp Ссылка / atomic / atomic_thread_fence упоминает только синхронизацию между ограждениями и атомарными объектами, ничего не говорится о синхронизации между ограждениями и обычными объектами. Я думаю, что join использует такой забор внутри, но могу ошибаться.   -  person Mikaka    schedule 11.02.2017
comment
Да. В противном случае join был бы бесполезен.   -  person T.C.    schedule 11.02.2017
comment
@ T.C. Разве множественная несинхронизированная перезапись success между 5 потоками технически не является UB?   -  person Richard Hodges    schedule 11.02.2017
comment
@RichardHodges В этом примере в него пишет только один поток ...?   -  person T.C.    schedule 11.02.2017
comment
@ T.C. о да. ты прав.   -  person Richard Hodges    schedule 11.02.2017
comment
@RichardHodges Привет. Прошу прощения за эту ошибку. Я изменил пример, потому что в реальной программе несколько потоков могут попытаться записать в переменную success, меняет ли это ситуацию?   -  person Mikaka    schedule 11.02.2017
comment
@ T.C. насчет того, что сейчас его отредактировали. Хотя я ожидаю, что это сработает, я думаю, что это UB.   -  person Richard Hodges    schedule 11.02.2017
comment
@RichardHodges Ага, теперь ясно очерченные носовые демоны.   -  person T.C.    schedule 11.02.2017


Ответы (1)


Приведенный выше код представляет собой гонку данных, и использование join не может изменить этот факт. Если бы только один поток записал в переменную, все было бы хорошо. Но у вас есть два потока, которые пишут в него, и между ними нет синхронизации. Это гонка за данными.

join просто означает «все побочные эффекты операции этого потока выполнены и теперь видны вам». Это не создает упорядочения или синхронизации между этим потоком и любым потоком, другим, кроме вашего собственного.

Если бы вы использовали atomic_bool, то это был бы не UB; он гарантированно окажется ложным. Но поскольку существует гонка данных, вы получаете чистый UB. Это могут быть правдивые, ложные или гнусные демоны.

person Nicol Bolas    schedule 11.02.2017
comment
Хорошо, я получаю часть неопределенного поведения. Но если предположить, что мы находимся на платформе, где запись в bool является атомарной, а мой поток (тот, который объединяет всех рабочих) - единственный, кто читает флаг success, будет join будет достаточно, чтобы гарантировать правильный порядок, и я всегда буду видеть false, если какой-то рабочий установил его, или я все равно могу получить true? - person Mikaka; 11.02.2017
comment
@Mikaka: Но если предположить, что мы находимся на платформе, где запись в bool является атомарной Нет. Вы спрашивали о C ++. Я говорю вам, что гарантирует C ++. Насколько произвольная платформа, которая, я надеюсь, будет вести себя разумно, зависит от вас и вашей платформы. Если вы хотите, чтобы это гарантированно сработало, используйте настоящий atomic<bool>. Если это платформа, которая ведет себя так, как вы думаете, тогда atomic<bool> будет просто тонкой оберткой вокруг bool. А если нет, то все равно будет правильно. - person Nicol Bolas; 11.02.2017