Card 04/ 09
Race Condition Example: Moving Money Between Two Accounts
Four lines, no increment operator, and a lost update all the same.
A race condition does not need a counter. Any code that reads a shared value, decides or computes from it, and writes back can lose an update, and a transfer between two accounts does all three.
void transfer(Account from, Account to, long pence) {
if (from.balance >= pence) {
from.balance -= pence;
to.balance += pence;
}
}Two threads each transfer a hundred pounds out of an account holding a hundred and fifty. Both read the balance on line 2, both find it sufficient, and both carry on. The account can end at minus fifty, with two hundred pounds paid out of a hundred and fifty; and because -= is itself a read and a write, the balance can also end wrong in other ways, depending on timing.
Put this beside two threads each running count++ on a shared field and losing increments, and the shared structure comes out: both read a value, computed from it, and wrote it back, and another thread got in between the read and the write. One is three characters and one is four lines; the mechanism is identical.
| Written | Reads | Then writes |
|---|---|---|
count++ | The count | The count plus one |
balance -= pence | The balance | The balance less the amount |
if (m.get(k) == null) m.put(k, v) | Whether the key is there | The key, if it was not |
The check on line 2 belongs to the same operation as the write on line 3, and nothing in the language says so. Recognising that shape is most of what it takes to spot a race before it ships.
← → move through sections and cards