My understanding of std::memory_order_acquire
and std::memory_order_release
is as follows:
Acquire means that no memory accesses which appear after the acquire fence can be reordered to before the fence.
Release means that no memory accesses which appear before the release fence can be reordered to after the fence.
What I don't understand is why with the C++11 atomics library in particular, the acquire fence is associated with load operations, while the release fence is associated with store operations.
To clarify, the C++11 <atomic>
library enables you to specify memory fences in two ways: either you can specify a fence as an extra argument to an atomic operation, like:
x.load(std::memory_order_acquire);
Or you can use std::memory_order_relaxed
and specify the fence separately, like:
x.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
What I don't understand is, given the above definitions of acquire and release, why does C++11 specifically associate acquire with load, and release with store? Yes, I've seen many of the examples that show how you can use an acquire/load with a release/store to synchronize between threads, but in general it seems that the idea of acquire fences (prevent memory reordering after statement) and release fences (prevent memory reordering before statement) is orthogonal to the idea of loads and stores.
So, why, for example, won't the compiler let me say:
x.store(10, std::memory_order_acquire);
I realize I can accomplish the above by using memory_order_relaxed
, and then a separate atomic_thread_fence(memory_order_acquire)
statement, but again, why can't I use store directly with memory_order_acquire
?
A possible use case for this might be if I want to ensure that some store, say x = 10
, happens before some other statement executes that might affect other threads.
atomic_thread_fence(std::memory_order_acquire)
is a true fence. See 1.10:5 Multi-threaded executions and data races [intro.multithread] in the standard, which says (quoting the draft n3797) "A synchronization operation without an associated memory location is a fence and can be either an acquire fence, a release fence, or both an acquire and release fence." In contrast,x.load(std::memory_order_acquire)
is an atomic operation that does an acquire operation onx
, it would be a synchronization operation if the value matches a store release into x. – Apiary(void)x.load(mo_acquire);
(which is an operation that is seldom used). – Mylesmylitta