liveBook · Manning

Memory ordering

As explained in Latency

Memory ordering is the set of rules governing the observable order in which memory operations become effective across concurrent execution contexts.

Memory ordering governs the observable order in which memory operations become effective across concurrent execution contexts. Modern CPUs may execute instructions out of order, so source-code order alone does not guarantee the order other threads observe. section 8.4

Memory barriers mark points where selected reorderings are prohibited. They provide finer-grained control than locks for lock-free and wait-free algorithms, while avoiding the overhead of full mutual exclusion. section 8.4

Ordering choices determine what another thread may observe:

  • Relaxed ordering provides no ordering guarantee between the data update and its completion signal. section 8.4.3
  • Release ordering ensures earlier writes become visible before a signal is published; Acquire ordering ensures a reader observes the signal before dependent data. section 8.4.3
  • Rust and C++ expose atomic ordering controls, whereas Python and JavaScript do not expose explicit memory barriers. section 8.4

Publishing data with a completion flag

One thread stores value = 1 and then sets done = true; another reads done and value. With Relaxed ordering, repeated execution can produce done = true with value = 0, violating the invariant. Replacing the signal store with Release and the signal load with Acquire prevents that observation. section 8.4.3

use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;

fn main() {
    for _ in 0..100000 {
        let value = Arc::new(AtomicUsize::new(0));
        let done = Arc::new(AtomicBool::new(false));

        let value1 = value.clone();
        let done1 = done.clone();
        let t1 = thread::spawn(move || {
            value1.store(1, Ordering::Relaxed);
            done1.store(true, Ordering::Relaxed);
        });
        let t2 = thread::spawn(move || {
           let done = done.load(Ordering::Relaxed);
           let value = value.load(Ordering::Relaxed);
           println!("value = {}, done = {}", value, done);
        });
        t1.join().unwrap();
        t2.join().unwrap();
    }
}

Use the Rust listing to reproduce the concurrent publication pattern and compare Relaxed behavior with Acquire and Release ordering. section 8.4.3

How Latency covers it

How deep it goes
Explains CPU reordering, barrier roles, ordering modes, and a runnable invariant-violation example.
Examples
Uses Rust atomics, a value-and-done flag pair, repeated execution, and sorted output to expose an invariant violation.
What it assumes
Builds from concurrent execution and CPU out-of-order execution before introducing explicit barriers and atomic ordering modes.
What you can do after
Apply it via Rust or C++ atomic APIs to publish data safely between concurrent threads.
How it connects
Connects memory barriers with atomic operations, locks, lock-free algorithms, wait-free synchronization, and visibility.

Also explained in

Compare how 2 books explain memory ordering →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Latency. The section links point to the parts of the book it draws on.