Race Conditions, Atomicity and Memory Models
Know the difference between data races and race conditions, why compound operations aren't atomic, and what happens-before edges each language gives you, from Java volatile to C++ acquire/release and Go channels.
Key points
- 1
A data race is unsynchronized conflicting access to one location; a race condition is a timing-dependent logic bug. Either can exist without the other.
- 2
x++, check-then-act and lazy initialization are read-modify-write sequences: atomic steps don't compose into an atomic whole. - 3
Visibility needs a happens-before edge: lock release/acquire, volatile or atomic write/read, thread start/join, channel send/receive.
- 4
Java volatile gives visibility and ordering, not atomicity of
count++; final fields are safe to publish only ifthisdoesn't escape the constructor. - 5
C++ memory orders: relaxed keeps only per-variable atomicity, release/acquire publishes earlier writes, seq_cst adds one global order (needed for store-buffering and IRIW).
- 6
In C, C++ and Go a data race is undefined or unsafe behaviour; Go races on multi-word values (slices, interfaces) can corrupt memory.
- 7
Avoid races by design: immutability, thread confinement, message passing, then locks or atomics for what remains shared.
Common traps
Thinking the GIL makes
counter += 1atomic: it compiles to separate load, add and store bytecodes.Using
volatilein C or C++ for thread synchronization; it gives no inter-thread ordering. Use atomics.Assuming release/acquire forbids every reordering: a store followed by a load to another variable can still be reordered.
Test yourself on Race Conditions, Atomicity and Memory Models
Ten questions, with the answer and explanation after each one.