C Mastery / Atomics and the C Memory Model
Part 10 — Concurrency

Atomics and the C Memory Model

This chapter covers C11 atomics and the memory model that defines how concurrent accesses are ordered.

Why This Matters

Atomics are the foundation of lock-free programming and the formal way to express synchronization. The memory model defines *which* operations are allowed to see *which* effects — without it, you cannot reason about concurrent code.

Prerequisites

Core Concept

An atomic operation is indivisible: no observer can see an intermediate state. C11 provides _Atomic types and functions in <stdatomic.h>.

#include <stdatomic.h>

atomic_int counter = 0;

atomic_fetch_add(&counter, 1);      /* atomic increment */
int v = atomic_load(&counter);      /* atomic read */

Atomic operations take an optional memory order argument (c.conc.6). The default is memory_order_seq_cst.

The C memory model

The model defines a happens-before relation that orders effects:

by happens-before.

Examples

Atomic flag (spinlock-style)

#include <stdatomic.h>

static atomic_flag busy = ATOMIC_FLAG_INIT;

void acquire(void)
{
    while (atomic_flag_test_and_set_explicit(&busy, memory_order_acquire))
        ; /* spin */
}

void release(void)
{
    atomic_flag_clear_explicit(&busy, memory_order_release);
}

Atomic counter

#include <stdatomic.h>

static atomic_int counter = 0;

int next(void)
{
    return atomic_fetch_add(&counter, 1);
}

How It Works

Atomics compile to hardware atomic instructions (lock xadd on x86, ldxr/stxr on ARM) with appropriate barriers. The memory model constrains compiler and CPU reordering so that a release in one thread synchronizes-with an acquire in another, making prior writes visible.

Variations

Lock-free vs. atomic

atomic_is_lock_free tells whether a given atomic type is truly lock-free on the target (some types may use a hidden lock). Use it when lock-freedom is a hard requirement.

Signal handlers

sig_atomic_t is a separate, signal-safe type, distinct from _Atomic.

Common Mistakes

Undefined Behavior

Portability

Under the Hood

The compiler emits fences/barriers as needed by the memory order. The CPU's memory model (e.g., x86's strong TSO vs. ARM's weak model) determines which barriers are necessary.

Practical Usage

Exercises

1. Implement an atomic counter and run it from multiple threads. 2. Use atomic_flag to build a spinlock. 3. Check atomic_is_lock_free for several types. 4. Explain why _Atomic is not the same as volatile.

Deep Challenge

Explain the difference between an atomic RMW (read-modify-write) and a sequence of atomic load then store, and why only the RMW is safe for a lock-free counter. Show the interleaving that breaks the load/store version.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.conc.atomic06