Sequential Consistency vs Relaxed Memory Ordering in C
This article examines the core differences between sequential
consistency (memory_order_seq_cst) and relaxed memory
ordering (memory_order_relaxed) in C low-level concurrency
via <stdatomic.h>. It explores how each model
influences compiler optimizations and hardware instruction reordering,
the operational guarantees each provides, and the practical performance
trade-offs involved when building concurrent algorithms.
Understanding the C11 Memory Model
Introduced in the C11 standard, the <stdatomic.h>
library provides explicit memory ordering semantics to control how reads
and writes to atomic variables propagate across processor cores. In
modern multi-core architectures, compilers and hardware dynamically
reorder instructions and utilize store buffers to maximize execution
throughput. Atomic memory orders tell both the compiler and the CPU
which reorderings are strictly forbidden and which are permissible.
Sequential
Consistency (memory_order_seq_cst)
Sequential consistency is the default memory order applied when using
atomic operations without specifying an explicit order in C (such as
atomic_store instead of
atomic_store_explicit).
- Global Order: Sequential consistency guarantees
that all threads observe all
seq_cstoperations in a single, universally agreed-upon total order. If Thread A stores to variableXand then to variableY, no thread in the system can observe the write toYbefore the write toX. - Synchronization and Ordering: Every sequentially consistent operation acts as both an acquire and release barrier, preventing surrounding reads and writes from being reordered before or after the atomic operation.
- Overhead: Maintaining a globally coherent timeline
requires hardware synchronization primitives, such as full memory fences
(e.g.,
MFENCEon x86) or locked instructions. This creates significant latency on architectures with relaxed hardware memory models, such as ARM or POWER.
Relaxed Memory
Ordering (memory_order_relaxed)
Relaxed memory ordering represents the weakest synchronization model provided by C.
- Atomicity Only: An operation executed with
memory_order_relaxedguarantees only that the read or write itself is atomic and will not produce torn reads or data races on that specific variable. It provides no synchronization with other memory operations. - Permissible Reordering: The compiler and the processor are free to reorder relaxed memory operations across other memory accesses (both non-atomic and other relaxed atomics), provided intra-thread program logic (program order) is preserved.
- No Global Timeline: Different threads may observe
relaxed modifications to distinct variables in completely different
orders. If Thread A writes to
Xand thenYusingmemory_order_relaxed, Thread B may observe the modification ofYbeforeX. - Performance: Because it eliminates hardware memory barriers, relaxed ordering translates to normal load and store instructions on almost all architectures, offering minimal execution overhead.
Key Differences
- Inter-Thread Synchronization:
memory_order_seq_cstcreates a clear "happens-before" relationship across threads, enabling flag-based coordination and synchronization of non-atomic payloads.memory_order_relaxedcannot be used to coordinate access to other shared state, as it does not enforce ordering constraints relative to other memory locations. - Visibility: Sequentially consistent operations establish a single global modification order visible to all CPU cores. Relaxed operations do not synchronize caches; operations become visible to other threads only as cache coherency protocols dictate, without timing or sequencing guarantees relative to other operations.
- Cost: Sequential consistency enforces conservative synchronization, preventing CPU pipelines from executing out-of-order memory operations. Relaxed ordering allows hardware to optimize instruction pipelines and memory buses freely.
Practical Application
Use memory_order_seq_cst when correctness requires a
deterministic, global timeline across multiple shared variables and
performance is secondary to preventing subtle concurrency bugs.
Use memory_order_relaxed exclusively when an operation
does not guard or depend on any other state. Common use cases include
monotonic performance counters, statistical metrics, or atomic reference
counter increments where the sequencing relative to other data is
irrelevant to program correctness.