C Mastery / Fences and Memory Barriers
Part 10 — Concurrency

Fences and Memory Barriers

This chapter covers memory fences (barriers): standalone operations that order memory accesses without themselves accessing an atomic object.

Why This Matters

Fences express ordering constraints that are not tied to a single atomic load or store. They are essential for correct lock-free code, and understanding them explains *why* acquire/release work on weak-memory architectures.

Prerequisites

Core Concept

A fence (C11: atomic_thread_fence) is a standalone memory-ordering operation:

#include <stdatomic.h>

atomic_thread_fence(memory_order_acquire);
atomic_thread_fence(memory_order_release);
atomic_thread_fence(memory_order_acq_rel);
atomic_thread_fence(memory_order_seq_cst);

Fences order *surrounding* memory operations rather than a single atomic operation. For example, a release fence ensures all prior writes are visible before any subsequent write; an acquire fence ensures subsequent reads happen after all prior reads.

Examples

Release/acquire using fences

atomic_int flag = ATOMIC_VAR_INIT(0);
int data = 0;

/* producer */
data = 42;
atomic_thread_fence(memory_order_release);
atomic_store_explicit(&flag, 1, memory_order_relaxed);

/* consumer */
while (atomic_load_explicit(&flag, memory_order_relaxed) == 0)
    ;
atomic_thread_fence(memory_order_acquire);
int v = data;   /* 42 */

This is the fence-based equivalent of the release/acquire message-passing pattern.

How It Works

A fence constrains the compiler and CPU from reordering memory operations across it. The compiler emits a hardware barrier instruction (mfence/lfence/ sfence on x86, dmb/dsb on ARM, fence on RISC-V) and/or marks its own reordering as forbidden.

Variations

Compiler barrier vs. hardware barrier

A compiler barrier (asm volatile("" ::: "memory")) only stops compiler reordering. A memory barrier/fence also stops CPU reordering. Use the right one for the job.

Fences vs. atomic operations

Atomic operations with acquire/release order are usually preferred because they are easier to reason about. Standalone fences are for cases where the ordering is independent of a specific atomic access.

Common Mistakes

Undefined Behavior

Portability

architecture-specific.

Under the Hood

On x86, loads are acquire-like and stores are release-like by default, so fences are mostly free; seq_cst needs mfence/lock prefixes. On ARM/RISC-V, fences map to explicit dmb/fence instructions.

Practical Usage

Exercises

1. Implement the fence-based message-passing example and verify with TSan. 2. Compare the generated assembly of a fence on x86 vs. ARM. 3. Explain the difference between a compiler barrier and a memory fence.

Deep Challenge

Implement a lock-free flag (release store + relaxed load) using fences, and explain why a plain volatile flag is insufficient. Show the assembly on a weak-memory architecture.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.conc.fence07
c.conc.barrier07