The lost update
Here is the most expensive small bug in software. Two threads both run counter += 1. Looks atomic. It is not. Under the hood it is three steps, just like your CPU simulator from lesson 1-2:
- read the current value of counter into the thread
- add 1 to it
- write the result back
The OS can pause a thread between any two steps (you will see why in unit 6). If thread B reads while thread A is paused between its read and its write, both read the same old value, both write back the same new value, and one increment vanishes. That is a race condition: correctness depends on lucky timing.
The block below forces the unlucky interleaving on purpose, so you can watch an update get lost, deterministically.
Code exercise · python
Run this deterministic re-enactment. Two "threads" A and B each try counter += 1, but B reads before A writes back. One update is lost.
Code exercise · python
Explore the real thing: four threads each add to a shared counter 100,000 times with NO protection. There is no expected output because the result is genuinely unpredictable, run it several times. On some Python versions you will see less than 400000, on others the timing rarely bites. That unpredictability is the point.
Why races are the worst kind of bug
- They pass tests: light test load rarely triggers the bad interleaving.
- They appear in production: real traffic means real concurrency.
- They vanish when you look: adding print statements changes the timing (a "heisenbug").
Real-world victims: double-spent account balances, two users grabbing the same username, inventory systems selling the last item twice. Any time you read shared data, compute, and write back, ask: what if someone else writes in between?
Quiz
A ticket site checks seats_left > 0 and then runs seats_left -= 1. Two requests arrive at nearly the same instant when seats_left is 1. What can happen?
Problem
What is the general name for a bug where the result depends on the unlucky timing or interleaving of concurrent operations? (two words)