Race conditions
Why threads that share data break.
Threads share data
Threads in one process share its memory, like the heap and global values. So if one thread changes a shared counter, every other thread sees the new value. Each thread still has its own stack for its local values. Separate processes don't share memory at all.
Race conditions
Adding 1 to a counter looks like one step, but the CPU does three: load the value into a register, add 1, and store it back. Another thread can run between any two of these steps. If two threads both load 5 before either stores, both store 6, and one update is lost. When the result depends on the timing of threads like this, it is a race condition.
Critical sections
Code that touches shared data is a critical section. Only one thread at a time may be inside it. This rule is called mutual exclusion. Then one thread's steps finish before the next starts.
Atomic operations
An atomic operation happens all at once. No other thread can see it half done or slip in between its steps. Hardware gives a few small atomic steps, such as an atomic add for a counter. A bigger change, like moving money between two accounts, takes many steps, so it still needs a critical section.
Bugs that hide
A race only goes wrong when threads switch at just the wrong moment. That may happen once in a million runs, so racy code can pass every test. Adding logging changes the timing, so the bug often vanishes when you look for it. Such a bug is called a heisenbug. So protect shared data by design.
Remember
- Threads share memory; each has its own stack.
- Adding 1 is load, add, store, and threads can interleave them.
- Only one thread at a time in a critical section.
- Atomic means all at once.
- Passing tests don't prove there is no race.