Будет ли вызывающий поток видеть изменения в локальных переменных после thread.join ()? - PullRequest
4 голосов
/ 01 мая 2019

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

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

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

1 Ответ

7 голосов
/ 01 мая 2019

Да, он должен возвращать 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 дочерним потоком).

...