Part 4 · 2 chapters · ~12 min
Memory Models Across Languages
Why reordering happens (compilers, CPUs, caches), happens-before, release and acquire, volatile in Java, Go's memory model and channels, Rust and C++ atomic orderings, JavaScript Atomics, double-checked locking, and safe publication.
7
Visibility and ordering
Without synchronisation, there is no guarantee that one thread ever sees another thread's write. The memory model is the contract that says when it must.
HAPPENS-BEFORE
why a thread can see a flag before the data it guards, and what fixes it
swipe the figure sideways, or tap expand for full screen
1/4
the expectation
Thread 1 writes the data, then sets a flag. Thread 2 waits for the flag, then reads the data. It seems the data must be 42.
write data, then flag; read flag, then dataintuition says 42
8
Safe publication
code
// Java: double-checked locking needs volatile, or other threads may see a half-constructed object
private static volatile Config config;
static Config get() {
Config c = config;
if (c == null) synchronized (Config.class) { c = config; if (c == null) config = c = load(); }
return c;
}
// Rust: publish with Release, read with Acquire
DATA.store(42, Ordering::Relaxed);
READY.store(true, Ordering::Release);
while !READY.load(Ordering::Acquire) {}
assert_eq!(DATA.load(Ordering::Relaxed), 42); // guaranteed by the release/acquire pair
// Go: the channel send happens before the receive completes
go func() { data = 42; done <- true }()
<-done; fmt.Println(data) // 42, guaranteed