Acquire semantics give an atomic operation a way to receive ordering from a release operation on the same atomic object. When the operations synchronize, work sequenced before the release happens before work sequenced after the acquire. This can make earlier writes to non-atomic data safe to read in another thread; the data itself need not be atomic. section 5.3.7
Acquiring a spinlock
A spinlock can loop on flag.test_and_set(std::memory_order_acquire) until it returns false. The thread that cleared the flag with flag.clear(std::memory_order_release) has finished its protected writes. That clear synchronizes with the successful acquisition, so those writes happen before the new lock holder accesses the protected data. Calls that find the flag set keep looping; they have not acquired the lock. section 5.3.7 section 5.2.2
Choose acquire for the operation that must establish ordering before a later read, not simply for every atomic access. Loads and read-modify-write operations can use std::memory_order_acquire; a store such as atomic_flag::clear() cannot. Without an explicit order, atomic operations default to std::memory_order_seq_cst. section 5.2.1 section 5.2.2
Reading a published stack node
In a lock-free stack, push() initializes a node and publishes it through a release compare-exchange on head. Before pop() reads the node's next field, a successful compare_exchange_strong() in increase_head_count() uses acquire ordering. It synchronizes with publication of the pointer it reads, establishing that the node initialization happens before the read. A failed exchange retries and can use relaxed ordering; the initial head load can also be relaxed. Once that acquire has established ordering, the later exchange in pop() need not repeat it merely to read the node's data. section 7.2.5
Listing 5.1. Implementation of a spinlock mutex using std::atomic_flag
class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex():
flag(ATOMIC_FLAG_INIT)
{}
void lock()
{
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
};
The complete spinlock places acquire on the successful test_and_set() path and release on clear(). section 5.2.2
void increase_head_count(counted_node_ptr& old_counter)
{
counted_node_ptr new_counter;
do
{
new_counter=old_counter;
++new_counter.external_count;
}
while(!head.compare_exchange_strong(old_counter,new_counter,
std::memory_order_acquire,std::memory_order_relaxed));
old_counter.external_count=new_counter.external_count;
}
The head-count exchange uses acquire on success and relaxed ordering on failure, matching the point where pop() needs the published node. section 7.2.5




