Есть ли у нас гарантии от компилятора и от процессора, что toolRunner будет создан после того как будет создан tool ?
Насколько я понимаю, компилятор вправе переупорядочить создание если нет видимого эффекта.
В данном случае нет разницы что будет создано первым, а значит можно поменять местами в целях оптимизации.
Исходя из этого напрашивается вывод, каждое создание std::thread должно быть ограничено барьером, чтобы запретить переупорядочивание как компилятором так и процессором и получить нужный нам порядок.
Здравствуйте, _NN_, Вы писали:
_NN>Есть ли у нас гарантии от компилятора и от процессора, что toolRunner будет создан после того как будет создан tool ?
Насколько помню, порядок инициализации переменных внутри одной единицы трансляции вполне определён. Проблема возникает, когда переменные определены в разных модулях
_NN>Насколько я понимаю, компилятор вправе переупорядочить создание если нет видимого эффекта.
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, Marty, Вы писали:
_NN>>>Насколько я понимаю, компилятор вправе переупорядочить создание если нет видимого эффекта.
M>>Не уверен
_NN>Почему не может? _NN>Скажем у нас есть
_NN>
_NN>int a{ 1 };
_NN>int b{ 2 };
_NN>
_NN>Порядок ведь не обязан быть сначала a потом b. _NN>Так и объекты посложнее если компилятор сможет понять, что видимый эффект не меняется от перестановки.
там по разному для разных типов
ключевое слово "точка следования"
_NN>Исходя из этого напрашивается вывод, каждое создание std::thread должно быть ограничено барьером, чтобы запретить переупорядочивание как компилятором так и процессором и получить нужный нам порядок.
Если барьеры добавляют наблюдаемое поведение, то наверное, а если нет — то как они запретят?
Здравствуйте, _NN_, Вы писали:
_NN>Есть ли у нас гарантии от компилятора и от процессора, что toolRunner будет создан после того как будет создан tool ?
_NN>Насколько я понимаю, компилятор вправе переупорядочить создание если нет видимого эффекта. _NN>В данном случае нет разницы что будет создано первым, а значит можно поменять местами в целях оптимизации.
Нет разницы, это если компилятору доступны в этой точке как конструктор Tool так и std::thread,
а также код всех функций которые вызывают эти конструкторы, в том числи и тело функции типа "pthread_create",
если речь идет от Linux платформе.
Иначе как компилятор может понять что можно переупорядочивать конструкторы?
Ведь невидимые ему реализации функций могут использовать одни и те же глобальные переменные,
и таким образом зависеть друг от друга.
_NN>Есть ли у нас гарантии от компилятора и от процессора, что toolRunner будет создан после того как будет создан tool ?
В этом вопросе самое важное слово "после". Вот в каком смысле "после"? Как вы это "после" понимаете, если учесть, что от этого "после" ничего не зависит и нет никаких способов, в некотором смысле физических, изнутри потока выполнения инструкций процессора узнать, что произошло раньше, а что позже?
_NN>Насколько я понимаю, компилятор вправе переупорядочить создание если нет видимого эффекта.
да.
_NN>В данном случае нет разницы что будет создано первым, а значит можно поменять местами в целях оптимизации.
да.
_NN>Исходя из этого напрашивается вывод, каждое создание std::thread должно быть ограничено барьером, чтобы запретить переупорядочивание как компилятором так и процессором и получить нужный нам порядок.
Возможно. Это зависит от того, какое поведение вы хотите запрограммировать.
_NN>Какие мысли ?
У меня есть подозрение, что вы не разделяете создание объекта toolRunner и запуск на выполнение — это две разные операции. Сначала создаётся объект toolRunner и продолжается выполнение основного потока, потом, параллельно, возможно намного позже, будет запущена нитка для toolRunner и ещё позже будет вызвана функция printf.
Более того, если убрать нитку, то ничего не поменяется:
Tool tool{};
printf("Starting tool");
Если конструктор Tool не имеет побочных эффектов, то нет гарантии, что вызов функции printf("Starting tool"); будет произведён после (что бы это не значило) создания объекта tool. Вообще говоря стандартом не запрещается параллельное выполнение этого кода, скажем, на двух разных процессорах: на одном создание объекта tool, а на втором выполнение функции printf.
По возможности, избавляться от кода, выполняемого до main. Будь то нетривиальные конструкторы глобальных объектов, или функции, помеченные атрибутом [constructor]].
Синглетоны Мейерса — меньшее зло.
Здравствуйте, _NN_, Вы писали:
_NN>Есть ли у нас гарантии от компилятора и от процессора, что toolRunner будет создан после того как будет создан tool ?
По идее да, ведь
The completion of the invocation of the constructor synchronizes-with (as defined in std::memory_order) the beginning of the invocation of the copy of f on the new thread of execution.
Synchronizes with
If an atomic store in thread A is a release operation, an atomic load in thread B from the same variable is an acquire operation, and the load in thread B reads a value written by the store in thread A, then the store in thread A synchronizes-with the load in thread B.
Also, some library calls may be defined to synchronize-with other library calls on other threads.
Здравствуйте, _NN_, Вы писали:
_NN>Переменные они локальные в функции.
Ааа, так это были локальные переменные? Тогда гарантия последовательности точно есть.
Переупорядочивание возможно лишь в том случае, если компилятор уверен в отсутствии побочных эффектов (что с std::thread, очевидно, уже не так). Но если побочных эффектов нет, то и пользователю незаметно, переупорядочил там что-то компилятор или сохранил.
_NN>Даже если не локальные по видимому можно переупорядочить тоже.
В одной единице трансляции — нет.
На уровне разных единиц трансляции — можно.
На уровне разных библиотек — недетерминированно, может даже варьироваться от запуска к запуску.
И вот именно поэтому, из-за того, что возможны гонки инициализации — стоит избавляться от энергичных синглетонов в пользу ленивых.
Здравствуйте, andrey.desman, Вы писали:
_NN>>Есть ли у нас гарантии от компилятора и от процессора, что toolRunner будет создан после того как будет создан tool ? AD>По идее да, ведь AD>
AD>The completion of the invocation of the constructor synchronizes-with (as defined in std::memory_order) the beginning of the invocation of the copy of f on the new thread of execution.
Здесь аргумент toolRunner'а никак не связан с tool, поэтому вышеприведённая цитата не относится к делу. Вот если эти объекты связать, то тогда — да, синхронизация будет:
BFE>Если конструктор Tool не имеет побочных эффектов, то нет гарантии, что вызов функции printf("Starting tool"); будет произведён после (что бы это не значило) создания объекта tool. Вообще говоря стандартом не запрещается параллельное выполнение этого кода, скажем, на двух разных процессорах: на одном создание объекта tool, а на втором выполнение функции printf.
Как я понимаю даже если компилятор понимает, что порядок менять нельзя, присваивание значения 10 может не отразится при чтении из другого потока. Верно ?
То есть даже в таком коде нам нужен барьер памяти, чтобы поток прочитал именно то, что нужно.
В таком случае atomic<int> также не спасает ведь инициализация не автомарная:
std::atomic<int> i{ 10 }; // 2) Initializes the underlying object with desired. The initialization is not atomic.
std::thread t( [&i] { printf("%d", i); } );
Здравствуйте, _NN_, Вы писали:
_NN>А если у нас есть инициализация переменной ?
Простая инициализация сама по себе мало что меняет, а вот использование переменной — да.
_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 изменился:
— будет выведено либо 10, либо 11.
Это даже на прямую не связано с многопоточностью, а с тем что если переменная участвует в выражении, то значение переменной берётся из вычисления предыдущего выражения. В данном случае предыдущее выражение — это инициализация. Поэтому инициализация должна быть завершена до того, как переменную будут использовать через ссылку.
Если я правильно понимаю, то проблема с not thread-safe инициализацией может возникнуть только при использовании не static глобальной переменной в нескольких потоках. Или если саму переменную пытаются использовать прямо в выражении инициализации типа такого:
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, _NN_, Вы писали:
_NN>>А если у нас есть инициализация переменной ? BFE>Простая инициализация сама по себе мало что меняет, а вот использование переменной — да.
_NN>>
_NN>>Как я понимаю даже если компилятор понимает, что порядок менять нельзя, присваивание значения 10 может не отразится при чтении из другого потока. Верно ? BFE>Нет. В этом случае i гарантированно будет инициализировано до создания ссылки на i.
Хорошо, i содержит 10, ссылка ссылается на памить i.
Но что если у нас два процессора где первый записал себе значение, но оно не дошло до памяти и второй процессор ещё его не видит ?
Кто даст гарантию если не явный барьер памяти, что есть видимость во всех процессорах ?
Здравствуйте, _NN_, Вы писали:
_NN>Хорошо, i содержит 10, ссылка ссылается на памить i. _NN>Но что если у нас два процессора где первый записал себе значение, но оно не дошло до памяти и второй процессор ещё его не видит ? _NN>Кто даст гарантию если не явный барьер памяти, что есть видимость во всех процессорах ?
я конечно подотстал в новых стандартах в с++.
Но у тебя в коде указан сначала вызов конструктора для Tool, а потом для std::thread
Я не вижу отличий между вызовом двух конструкторов последовательно в одном потоке один за другим (что ты и делаешь — у тебя конструкторы в одном потоке вызываются) и просто вызовом двух функций последовательно в одном потоке.
Например
a();
b();
как компилятор может сначала вызвать b() перед a()?
у меня это в голове не укладывается. Такого быть не должно.
Поэтому мой ответ такой. Конструктор Tool полностью отработает до того как вызовется конструктор std::thread
и поэтому printf("Starting tool") вызовется когда tool уже создан.