If you like to deal with fences, then a.load(memory_order_acquire)
is equivalent to a.load(memory_order_relaxed)
followed by atomic_thread_fence(memory_order_acquire)
. Similarly, a.store(x,memory_order_release)
is equivalent to a call to atomic_thread_fence(memory_order_release)
before a call to a.store(x,memory_order_relaxed)
. memory_order_consume
is a special case of memory_order_acquire
, for dependent data only. memory_order_seq_cst
is special, and forms a total order across all memory_order_seq_cst
operations. Mixed with the others it is the same as an acquire for a load, and a release for a store. memory_order_acq_rel
is for read-modify-write operations, and is equivalent to an acquire on the read part and a release on the write part of the RMW.
The use of ordering constraints on atomic operations may or may not result in actual fence instructions, depending on the hardware architecture. In some cases the compiler will generate better code if you put the ordering constraint on the atomic operation rather than using a separate fence.
On x86, loads are always acquire, and stores are always release. memory_order_seq_cst
requires stronger ordering with either an MFENCE
instruction or a LOCK
prefixed instruction (there is an implementation choice here as to whether to make the store have the stronger ordering or the load). Consequently, standalone acquire and release fences are no-ops, but atomic_thread_fence(memory_order_seq_cst)
is not (again requiring an MFENCE
or LOCK
ed instruction).
An important effect of the ordering constraints is that they order other operations.
std::atomic<bool> ready(false);
int i=0;
void thread_1()
{
i=42;
ready.store(true,memory_order_release);
}
void thread_2()
{
while(!ready.load(memory_order_acquire)) std::this_thread::yield();
assert(i==42);
}
thread_2
spins until it reads true
from ready
. Since the store to ready
in thread_1
is a release, and the load is an acquire then the store synchronizes-with the load, and the store to i
happens-before the load from i
in the assert, and the assert will not fire.
2) The second line in
atomicVar.store(42);
std::atomic_thread_fence(std::memory_order_seq_cst);
is indeed potentially redundant, because the store to atomicVar
uses memory_order_seq_cst
by default. However, if there are other non-memory_order_seq_cst
atomic operations on this thread then the fence may have consequences. For example, it would act as a release fence for a subsequent a.store(x,memory_order_relaxed)
.
3) Fences and atomic operations do not work like mutexes. You can use them to build mutexes, but they do not work like them. You do not have to ever use atomic_thread_fence(memory_order_seq_cst)
. There is no requirement that any atomic operations are memory_order_seq_cst
, and ordering on non-atomic variables can be achieved without, as in the example above.
4) No these are not equivalent. Your snippet without the mutex lock is thus a data race and undefined behaviour.
5) No your assert cannot fire. With the default memory ordering of memory_order_seq_cst, the store and load from the atomic pointer p
work like the store and load in my example above, and the stores to the array elements are guaranteed to happen-before the reads.