277. Release/Acquire — Publish Data Through a Flag, Not Just the Flag
This morning’s swap bite ended with a warning: once the winning thread writes data that other threads read, Relaxed stops being enough. Here’s the two-line fix.
The classic pattern: one thread computes a result, then raises a flag so others know it’s ready. With Relaxed everywhere, that’s broken:
| |
Relaxed only makes each individual operation atomic — it says nothing about the order two different atomics become visible in. The compiler or CPU may commit the READY store before the RESULT store, so a reader sees the flag up but the data stale.
The fix is a pair, one ordering on each side of the flag:
| |
When an Acquire load sees the value written by a Release store, everything the writer did before the store is visible to the reader after the load. The flag becomes a one-way gate for all the writes behind it — note RESULT itself can stay Relaxed; the flag pair carries the synchronization.
Full picture, verified with a scoped thread:
| |
Two things worth knowing. First, the pairing only kicks in when the Acquire load actually sees the Released value — that’s why the reader spins. Second, you won’t catch the Relaxed bug on your x86 laptop: the hardware is strongly ordered and hides it. Your ARM build server is not so forgiving, and the compiler is allowed to reorder either way.
Rule of thumb: Release on the store that publishes, Acquire on the load that consumes, Relaxed for lone counters and flags that guard nothing. This pair is exactly what Mutex unlock/lock and OnceLock do for you under the hood — reach for those first; reach for the raw pair when you can name what’s being published.
Stable since Rust 1.0, on every Atomic* type.