Предположим, у нас есть
std::atomic<int> x = 0;
// thread 1
foo();
x.store(1, std::memory_order_relaxed);
// thread 2
assert(x.load(std::memory_order_relaxed) == 1);
bar();
Во-первых, нет никакой гарантии, что поток 2 будет соблюдать значение 1 (то есть утверждение может сработать).Но даже если поток 2 действительно наблюдает значение 1, в то время как поток 2 выполняет bar()
, он может не наблюдать побочные эффекты, генерируемые foo()
в потоке 1. И если foo()
и bar()
обращаются к одному и тому же неатомарномупеременных, может возникнуть гонка данных.
Теперь предположим, что мы изменили пример на:
std::atomic<int> x = 0;
// thread 1
foo();
x.store(1, std::memory_order_release);
// thread 2
assert(x.load(std::memory_order_acquire) == 1);
bar();
До сих пор нет гарантии, что поток 2 соблюдает значение 1;В конце концов, может случиться так, что загрузка происходит перед магазином.Однако в этом случае , если поток 2 наблюдает значение 1, то хранилище в потоке 1 синхронизируется с загрузкой в потоке 2. Это означает, что все, что упорядочено до того, как хранилище в потоке 1 произойдет довсе, что упорядочено после загрузки в потоке 2. Следовательно, bar()
увидит все побочные эффекты, вызванные foo()
, и если они оба получат доступ к одним и тем же неатомарным переменным, гонка данных не произойдет.
Итак, как вы можете видеть, свойства синхронизации операций в x
ничего не говорят о том, что происходит с x
.Вместо этого синхронизация налагает порядок на , окружающий операции в двух потоках.(Следовательно, в связанном примере результат всегда равен 5 и не зависит от порядка в памяти; свойства синхронизации операций выборки-добавления не влияют на эффект самих операций выборки-добавления.)