Изменение неатомарных объектов с помощью указателя не _Atomic
не является по своей природе гонкой данных UB. (например, у вас мог бы быть алгоритм, который int *p = shared_ptr++;
мог бы заставить каждый поток захватывать свой собственный слот в неатомарном массиве.)
Но в этом случае у вас есть явный случай UB, потому что у вас есть 2 потока, обращающихся к основному tmp.first
, и они оба не читают.
Магазин с mo_release
упорядочен после всех предыдущих хранилищ (и загрузок), включая неатомарные хранилища, такие как ms->first = ...
. В этом суть релиз-магазинов против расслабленных.
Но недостаток в ваших рассуждениях заключается в шаге 1: atomic_store_explicit(&instance, ms, memory_order_release)
в set_first
только синхронизируется с получает нагрузки, которые видят сохраненное значение! Получает нагрузки в других потоках волшебным образом не ждите релиз магазина, который еще не произошел. Гарантия заключается в том, что если / когда вы do загрузите значение, хранящееся в хранилище релизов 1 , вы также увидите все более ранние вещи из этого потока.
Если загрузка получения происходит перед выпуском хранилища (в глобальном порядке, если он есть), синхронизация с ним отсутствует.
Приобретение-загрузка в обоих потоках может происходить одновременно , и тогда лиса оказывается в курятнике: ms -> first = i++;
и uint64_t current = ms -> first;
работают без синхронизации.
Совершенно неважно, что записывающий поток позже сделает хранилище релизов, чтобы сохранить то же значение обратно в instance
.
Сноска 1:
(Язык "release-sequence" в стандарте расширяет это, чтобы увидеть результат операции RMW, которая изменила результат начального release-store, и т. Д.)
Что касается других потоков, то atomic_load_explicit
в set_first
в основном не имеет значения. С тем же успехом можно вытащить его из петли.