Sequential Consistency in the C++ Memory Model
A comprehensive technical breakdown of Sequential Consistency (std::memory_order_seq_cst) based on Rainer Grimm’s insights into the foundational mechanics of C++ multithreading.
Table of Contents
Overview & Core Foundations
In C++11, the C++ language formally introduced a standardized memory model. At the foundation of this memory model sit atomic operations, which prevent data races when multiple threads access shared memory.
By default, all atomic operations in C++ utilize Sequential Consistency (std::memory_order_seq_cst). First formally defined by Leslie Lamport in 1979, sequential consistency establishes the strongest correctness guarantees available in the C++ memory model.
The Sequential Consistency Guarantees
- Source Code Execution Order (Intra-Thread): Program statements executed by a single thread strictly follow program (source code) order. No instruction can cross an atomic boundary via compiler or CPU reordering.
- Global Total Order (Inter-Thread): All operations on all threads follow a single, globally agreed-upon time clock. Every thread observes the execution sequence of other threads in the exact order in which they executed.
Modern C++ Concurrency Memory Model
Formal Execution Rules
Sequential consistency relies on formal relationships established by the C++ specification to guarantee program-wide visibility:
+--------------------------+ +---------------------------+
| Sequenced-Before Rule | -----> | Synchronizes-With Rule |
| (Intra-Thread Precedence)| | (Inter-Thread Handshake) |
+--------------------------+ +---------------------------+
|
v
+---------------------------+
| Happens-Before Relation |
| (Guaranteed Visibility) |
+---------------------------+
| Relationship | Execution Scope | Description |
|---|---|---|
| Sequenced-Before | Single-Thread | Statement $A$ comes before Statement $B$ in local execution order. |
| Synchronizes-With | Multi-Thread | An atomic write in Thread 1 is observed by an atomic read in Thread 2 on the same variable. |
| Happens-Before | Program-Wide | Combines sequenced-before and synchronizes-with to establish total memory visibility order. |
Technical Case Study: Producer-Consumer Pattern
The following example demonstrates deterministic synchronization between a producer thread and a consumer thread using std::memory_order_seq_cst.
#include <atomic>
#include <iostream>
#include <string>
#include <thread>
std::string work;
std::atomic<bool> ready{false};
void producer() {
// 1. Non-atomic modification
work = "done";
// 2. Atomic store (std::memory_order_seq_cst by default)
ready.store(true);
}
void consumer() {
// 3. Atomic load polling (std::memory_order_seq_cst by default)
while (!ready.load()) {
std::this_thread::yield();
}
// 4. Guaranteed to observe work = "done" without a data race
std::cout << work << std::endl;
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}Execution Order Breakdown
work = "done"is sequenced-beforeready.store(true).ready.store(true)synchronizes-withready.load()in the consumerwhile-loop.- The consumer
while-loop is sequenced-beforestd::cout << work. - Transitively,
work = "done"happens-beforestd::cout << work, ensuring zero data races on the non-atomic stringwork.
Deep-Dive: Hardware & Compiler Overhead
While sequential consistency offers intuitive reasoning, its key trade-off is performance and hardware overhead.
To maintain a single global total order across core boundaries:
- x86 / x64: Standard loads and stores already enforce strict ordering, but sequential stores require expensive
LOCK XCHGorMFENCEinstructions to prevent store-load reordering. - ARMv8 / Weak Architectures: Emits pipeline-blocking instructions (such as
LDARandSTLR) which force CPU store buffers to flush completely before proceeding.
Comparison of C++ Memory Models
Sequential consistency sits at the top of a spectrum of C++ synchronization strength:
[ Strongest / Easiest ] [ Weakest / Complex ]
Sequential Consistency -----> Acquire-Release Semantics -----> Relaxed Semantics
| Feature | Sequential Consistency (seq_cst) | Acquire-Release (acquire/release) | Relaxed (relaxed) |
|---|---|---|---|
| Global Order | Yes (Single program clock) | No (Pairwise threads only) | No global order |
| Reordering Barriers | Full two-way barrier | One-way barrier | No ordering barriers |
| Data Race Prevention | Yes | Yes | Yes (On the target variable only) |
| Intuitive Reasoning | High | Medium | Low (Requires formal verification) |