Увидит ли вызывающий поток изменения локальных переменных после thread.join()?

В простейшем возможном примере предположим, что у меня есть функция, которая запускает поток, который, в свою очередь, устанавливает значение локальной переменной в true. Мы присоединяемся к потоку, затем выходим из функции.

bool func() {
    bool b = false;
    std::thread t([&]() { b = true; }); 
    t.join();
    return b;
}

Будет ли эта функция возвращать true или поведение не определено?


person Alecto Irene Perez    schedule 01.05.2019    source источник
comment
Неважно, локальная переменная или глобальная, главное, чтобы ее время жизни было достаточным (как здесь).   -  person curiousguy    schedule 02.06.2019


Ответы (1)


Да, он должен вернуть true.

[thread.thread.member]

void join();

4 Эффекты : блокируется до тех пор, пока поток, представленный *this, не завершится.

5 Синхронизация : завершение потока, представленного *this, синхронизируется с ([intro.multithread]) соответствующим успешным возвратом join().

Таким образом, выполнение потока, представленного дескриптором, и связанные с ним побочные эффекты выполняются до того, как join вернется в контекст вызова.

Пример

Давайте посмотрим на две функции, которые отличаются только тем, когда они присоединяются к потоку:

int count_A() {
    int counter = 0;
    bool flag(true);
    auto t = std::thread([&]{flag = false;});

    while(flag) {    // infinite loop - flag never synchronized
        ++counter;
    }
    t.join();        // joins thread after loop exits
    return counter;
}
int count_B() {
    int counter = 0;
    bool flag(true);
    auto t = std::thread([&]{flag = false;});

    t.join();       // joins thread before loop, forcing synchronization
    while(flag) {
        ++counter;
    }
    return counter;
}

При компиляции с g++ версии 8.2 с оптимизацией -O3 вызов count_A приводит к бесконечному циклу, поскольку компилятор предполагает, что flag всегда истинно.

С другой стороны, вызов count_B просто вернет значение 0. Поскольку значение flag проверяется после thread.join(), оно загружается повторно, а флаг равен false, поэтому цикл while не выполняется.

Обратите внимание, что если flag изменить на atomic_bool, то count_A будет увеличивать счетчик до тех пор, пока флаг не будет установлен в false, а функция не войдет в бесконечный цикл (вместо того, чтобы вернуться после того, как flag будет устанавливается в false дочерним потоком).

person Community    schedule 01.05.2019
comment
@ J.Antonio - Вы собирались ответить на этот пост самостоятельно? - person StoryTeller - Unslander Monica; 01.05.2019
comment
Нет, но после того, как вы ответили на него, я поиграл с кодом, чтобы лучше понять ситуацию, и решил, что это будет хорошим дополнением к ответу. Я очень ценю, что вы нашли время, чтобы найти соответствующую часть стандарта, которая касается этого - person Alecto Irene Perez; 01.05.2019
comment
@ J.AntonioPerez - НП. Я просто подумал, что, возможно, врезался в очередь, пока вы публиковали ответ. Для потомков стоит упомянуть, что можно ответить самому себе еще до того, как опубликовать вопрос, есть флажок, который открывает панель ответов над вопросом. - person StoryTeller - Unslander Monica; 01.05.2019
comment
Спасибо, что дали мне знать! Честно говоря, я не знал, что говорится в стандарте по этому поводу, и я не чувствовал, что могу дать исчерпывающий ответ. - person Alecto Irene Perez; 01.05.2019