Здравствуйте, _NN_, Вы писали:
_NN>А если у нас есть инициализация переменной ?
Простая инициализация сама по себе мало что меняет, а вот использование переменной — да.
_NN>_NN>int i{ 10 };
_NN>std::thread t( [&i] { printf("%d", i); } );
_NN>
_NN>Как я понимаю даже если компилятор понимает, что порядок менять нельзя, присваивание значения 10 может не отразится при чтении из другого потока. Верно ?
Нет. В этом случае i гарантированно будет инициализировано до создания ссылки на i.
Так как i — ссылка, то напечатано будет фактическое значение i на момент копирования значения i при передаче в функцию printf. Т.е. вот такой код:
int i{ 10 };
std::thread t( [&i] { printf("%d", i); } );
i += 1;
Может напечатать 11, (а может любое другое значение, на архитектуре, где i не атомарная переменная).
_NN>То есть даже в таком коде нам нужен барьер памяти, чтобы поток прочитал именно то, что нужно.
Если переменная i не меняется, то никаких проблем.
Ну и вот такой код однозначно выведет 10:
int i{ 10 };
std::thread t( [i] { printf("%d", i); } );
i += 1;
_NN>В таком случае atomic<int> также не спасает ведь инициализация не автомарная:
_NN>_NN>std::atomic<int> i{ 10 }; // 2) Initializes the underlying object with desired. The initialization is not atomic.
_NN>std::thread t( [&i] { printf("%d", i); } );
_NN>
Этот код не скомпилируется, но если написать
std::atomic<int> i{ 10 }; // 2) Initializes the underlying object with desired. The initialization is not atomic.
std::thread t( [&i] { printf("%d", i.load()); } );
то будет выведено значение 10 или другое корректное значение, если i изменился:
std::atomic<int> i{ 10 };
std::thread t( [&i] { printf("%d", i.load()); } );
i += 1;
— будет выведено либо 10, либо 11.
Это даже на прямую не связано с многопоточностью, а с тем что если переменная участвует в выражении, то значение переменной берётся из вычисления предыдущего выражения. В данном случае предыдущее выражение — это инициализация. Поэтому инициализация должна быть завершена до того, как переменную будут использовать через ссылку.
Если я правильно понимаю, то проблема с not thread-safe инициализацией может возникнуть только при использовании не static глобальной переменной в нескольких потоках. Или если саму переменную пытаются использовать прямо в выражении инициализации типа такого:
std::atomic<int> i{[&i]{ std::thread t( [&i] { printf("%d", i.load()); } ); return 2;}()};
— это, вообще говоря, разновидность UB вида
int i = 1 + i;