Study notes · 12% of the exam

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. 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. 2

    x++, check-then-act and lazy initialization are read-modify-write sequences: atomic steps don't compose into an atomic whole.

  3. 3

    Visibility needs a happens-before edge: lock release/acquire, volatile or atomic write/read, thread start/join, channel send/receive.

  4. 4

    Java volatile gives visibility and ordering, not atomicity of count++; final fields are safe to publish only if this doesn't escape the constructor.

  5. 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. 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. 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 += 1 atomic: it compiles to separate load, add and store bytecodes.

  • Using volatile in 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.