Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Memory visibility and ordering

โญโญโญ advancedโณ 16 min read๐Ÿ“ Lesson 197 of 265

Why a bool flag lies: caches, compiler reordering, Volatile, Interlocked โ€” what the runtime actually guarantees.

Memory visibility and ordering

When two threads touch the same variable, the hardware and the JIT each conspire to make things faster โ€” caches absorb writes, the compiler hoists reads out of loops into registers. A bool flag can spin true forever because the reading loop cached it. The fix is not "thread safety" hand- waving; it is a concrete memory model:

// Writer thread                          // Reader thread
_flag = true;                             while (!_flag) { }        // may never see it!

Volatile.Write(ref _flag, true);          while (Volatile.Read(ref _flag) == false) { }
// Volatile.Write = release: prior writes become visible before it
// Volatile.Read  = acquire: subsequent reads see everything before it

The guarantees, weakest to strongest:

  • Plain field: no guarantees across threads. JIT may cache; CPU may reorder.
  • volatile field: acquire/release semantics โ€” no caching, no reordering of accesses around it. But volatile does NOT make compound operations atomic: volatile int x; x++; still loses updates (read-modify-write is three steps).
  • Interlocked: full atomicity for read-modify-write (Add, Increment, CompareExchange) โ€” the only correct tool for counters shared across threads.
  • lock: mutual exclusion AND the same acquire/release fences โ€” a lock makes everything inside visible, which is why locking one variable fixes visibility of every write before it.

The teaching rule: use Interlocked for counters, lock for invariants spanning multiple variables, Volatile for flags. Reach for anything weaker only with a measurement that proves you need the speed.