A fence constrains the ordering of memory operations without modifying data. Typically paired with atomic operations using relaxed ordering, fences can establish synchronization relationships that relaxed operations alone do not provide. A synchronizes-with relationship requires a release and an acquire side, connected through an atomic store and load that reads its value. section 5.3.5
Listing 5.12. Relaxed operations can be ordered with fences
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
x.store(true,std::memory_order_relaxed); #1
std::atomic_thread_fence(std::memory_order_release); #2
y.store(true,std::memory_order_relaxed); #3
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); #4
std::atomic_thread_fence(std::memory_order_acquire); #5
if(x.load(std::memory_order_relaxed)) #6
++z;
}
int main()
{
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0); #7
}
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":7,\"ch\":59},{\"line\":7,\"ch\":61}],[{\"line\":8,\"ch\":59},{\"line\":8,\"ch\":61}],[{\"line\":9,\"ch\":59},{\"line\":9,\"ch\":61}],[{\"line\":13,\"ch\":59},{\"line\":13,\"ch\":61}],[{\"line\":14,\"ch\":59},{\"line\":14,\"ch\":61}],[{\"line\":15,\"ch\":59},{\"line\":15,\"ch\":61}],[{\"line\":27,\"ch\":59},{\"line\":27,\"ch\":61}]]"}
!@%STYLE%@!
The example places a release fence between relaxed stores and an acquire fence after a relaxed load observes the published value. This orders the earlier store to x before the reader’s load from x. section 5.3.5
Listing 5.13. Enforcing ordering on non-atomic operations
#include <atomic>
#include <thread>
#include <assert.h>
bool x=false; #1
std::atomic<bool> y;
std::atomic<int> z;
void write_x_then_y()
{
x=true; #2
std::atomic_thread_fence(std::memory_order_release);
y.store(true,std::memory_order_relaxed); #3
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); #4
std::atomic_thread_fence(std::memory_order_acquire);
if(x) #5
++z;
}
int main()
{
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0); #6
}
The same pattern can order a non-atomic variable: the atomic y communicates between threads, while the fences order the write and read of x and prevent a data race on it. section 5.3.6


