11/32 · 5 min
When two threads touch the same thing
Two threads each add one to a shared counter, a thousand times. The final value should be two thousand. It is usually less, and by a different amount each run.
Why
counter += 1 is not one operation. It is three:
load counter into a register
add 1
store the register backA switch between the load and the store loses an increment:
thread A: load -> 41
thread B: load -> 41
thread A: add, store -> 42
thread B: add, store -> 42 one increment goneThe result depends on exactly when the scheduler interrupts, which is why the bug is intermittent, disappears under a debugger, and never reproduces on the machine where you are looking for it.
The critical section
The stretch of code that touches shared state is the critical section, and correctness needs three properties:
Mutual exclusion. At most one thread inside at a time.
Progress. If nobody is inside, a thread wanting in must get in. A protocol where everyone politely waits forever satisfies mutual exclusion and is useless.
Bounded waiting. A thread cannot be passed over indefinitely while others go repeatedly.
What does not fix it
if not busy: # check
busy = True # and set, two steps
...Two threads can both pass the check before either sets the flag. Any fix built from ordinary reads and writes has this problem somewhere, which is why the hardware provides an instruction that tests and sets in one indivisible step, and every lock is built on it.
Atomic operations
For a counter specifically, an atomic increment is enough and is cheaper than a lock, because it is one instruction rather than a kernel call.
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet(); // one indivisible operationRecognising the risk
Ask of any shared variable: is there a point between reading it and writing it where another thread could interfere? If yes, and it is not protected, it is a race whether or not you have seen it fail.
Data that is never written after creation is safe by construction, which is the strongest argument for immutability: a value that cannot change cannot be raced.