Skip to content

17. Race Condition / Critical Section

Race condition: outcome depends on unpredictable thread timing/ordering. Even counter++ is 3 instructions (LOAD, ADD, STORE) — a context switch mid-sequence can lose an increment. Race conditions are non-deterministic: they work most of the time, then fail unpredictably, making them hard to debug.

Critical section: code touching shared resources that must run in only one thread at a time. A valid solution needs:

  1. Mutual exclusion — only one thread inside at a time
  2. Progress — entry decisions can't be postponed indefinitely
  3. Bounded waiting — a limit on how many times others can cut in ahead of a waiting thread

Solutions: Peterson's Solution (software, 2 threads, busy-waits, breaks on modern out-of-order CPUs), hardware primitives (Test-And-Set, Compare-And-Swap — basis of lock-free structures), and mutexes/locks (the practical, OS-provided option).

Spinlock vs blocking lock: a spinlock busy-waits (burns CPU, avoids context-switch overhead — good for very short sections); a blocking lock (mutex) sleeps the thread — better for longer sections.

Types: read-write races, write-write races, and check-then-act races (e.g., if (balance >= amount) balance -= amount; — a switch right after the check lets two threads both pass it).

Detection/avoidance: ThreadSanitizer, Helgrind, static analysis for detection; mutexes, atomics, immutability, thread-local storage, and CAS-based lock-free structures for avoidance.