liveBook · Manning

Memory barrier

As explained in C++ Concurrency in Action, Second Edition

A fence is a synchronization operation that constrains the ordering of memory operations without itself accessing the protected object.

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

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Explains release/acquire synchronization and ordering non-atomic accesses, but does not provide an exhaustive fence reference.
Examples
Uses atomic x and y across two threads, then replaces x with a non-atomic bool to illustrate enforced ordering.
What you can do after
Apply release and acquire fences to order relaxed atomic operations or protect ordered accesses to non-atomic data.
How it connects
Links fences to relaxed atomics, release/acquire ordering, happens-before, synchronizes-with, and data-race prevention.

Also explained in

Compare how 3 books explain memory barrier →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.