Приведет ли расслабленный порядок памяти к бесконечному циклу здесь?
Соответствующий код:
#include <atomic>
#include <thread>
std::atomic_bool stop(false);
void wait_on_stop() {
while (!stop.load(std::memory_order_relaxed));
}
int main() {
std::thread t(wait_on_stop);
stop.store(true, std::memory_order_relaxed);
t.join();
}
Поскольку здесь используется std::memory_order_relaxed
, я предполагаю, что компилятор может свободно переупорядочивать stop.store()
после t.join()
. В результате, t.join()
никогда не вернется. Правильно ли это рассуждение?
Если да, то изменение stop.store(true, std::memory_order_relaxed)
на stop.store(true)
решит проблему?
2 ответа:
[вступление.прогресс] / 18:
Реализация должна гарантировать, что последнее значение (в модификации порядок), назначенный атомарной операцией или операцией синхронизации, станет видимый для всех других потоков в течение конечного периода времени.
[атомика.порядок] / 12:
Реализации должны сделать атомарные хранилища видимыми для атомарных нагрузок в разумные сроки.
Это необязательная рекомендация. Если ваша реализация следует им - как и положено качественным реализациям-вы прекрасны. В противном случае, вы ввернуты. В обоих случаях, независимо от используемого порядка памяти.
Абстрактная машина C++ не имеет понятия "переупорядочивания". В абстрактной семантике основной поток хранится в атомарном, а затем блокируется, и поэтому, если реализация делает хранилище видимым для нагрузок в течение конечного промежутка времени, то другой поток загрузит это сохраненное значение в течение конечного промежутка времени и завершит работу. Напротив, если реализация не делает этого по какой-либо причине, то ваш другой поток будет петлять вечно. Используемый порядок памяти не имеет значения.
Я никогда не считал рассуждения о "переупорядочивании" полезными. Он смешивает низкоуровневые детали реализации с высокоуровневой моделью памяти и, как правило, делает вещи более запутанными, а не менее.
Любая функция, определение которой недоступно в текущей единице перевода, считается функцией ввода-вывода. Предполагается, что такие вызовы вызывают побочные эффекты, и компилятор не может переместить следующие операторы, чтобы предшествовать вызову, или предыдущие операторы, чтобы следовать за вызовом.
Чтение объекта, обозначенного изменчивым значением glvalue ([basic.lval]), изменение объекта, вызов функции ввода-вывода библиотеки или вызов функции, выполняющей любое из следующих действий: все эти операции являются побочными эффектами, то есть изменениями в состоянии среды выполнения. Оценка выражения (или подвыражения) в общем случае включает как вычисление значения (включая определение идентичности объекта для оценки glvalue и извлечение значения, ранее присвоенного объекту для оценки prvalue), так и инициирование побочных эффектов. Когда возвращается вызов функции ввода-вывода библиотеки или вычисляется доступ через изменчивое значение glvalue, побочным эффектом является считается завершенным, даже если некоторые внешние действия, подразумеваемые вызовом (например, сам ввод-вывод) или волатильным доступом, возможно, еще не завершены.
И
Каждое вычисление значения и побочный эффект, связанный с полным выражением, упорядочиваются перед каждым вычислением значения и побочным эффектом, связанным со следующим полным выражением, подлежащим оценке.Здесь
std::thread
конструктор иstd::thread::join
являются такими функциями (они в конечном счете вызывают специфичные для платформы функции потока недоступны в текущем ту) с побочными эффектами.stop.store
также вызывает побочные эффекты (хранилище памяти-это побочный эффект). Следовательно,stop.store
не может быть перемещено доstd::thread
конструктора или послеstd::thread::join
вызовов.