Imagine two ATMs depositing $10 into an account with a $0 balance. Both read $0, both calculate $10, and both save $10. One deposit disappears. A race condition occurs when concurrent tasks access shared state and the correct result depends on which task gets there first. section 8.2
| ATM A deposit() |
ATM B deposit() |
Balance |
|
|---|---|---|---|
| Get balance |
← |
0 |
|
| Get balance |
← |
0 |
|
| Add 10 |
|||
| Add 10 |
|||
| Write back the result |
→ |
10 |
|
| Write back the result |
→ |
10 |
Follow the two deposits step by step: each ATM reads the old balance before either writes its result. section 8.2
A deposit may look like one operation, but it reads a balance, adds an amount, and writes the result. Threads can switch between those steps, whether they run on different processor cores or take turns under the OS scheduler. Compiler and hardware optimizations can also affect when memory operations become visible. That makes race-condition errors difficult to reproduce. section 8.2
An unreliable balance
In a Python test, many ATM threads each deposit $10 and withdraw $10 from one unsynchronized account. The expected final balance is $0; one run instead produces $380. The incorrect amount is a symptom, not a result to expect on every run. section 8.2
# Chapter 8/race_condition/synced_bank_account.py
from threading import Lock
from unsynced_bank_account import UnsyncedBankAccount
class SyncedBankAccount(UnsyncedBankAccount):
def __init__(self, balance: float = 0):
super().__init__(balance)
self.mutex = Lock()
def deposit(self, amount: float) -> None:
self.mutex.acquire() #1
super().deposit(amount)
self.mutex.release() #2
def withdraw(self, amount: float) -> None:
self.mutex.acquire() #1
super().withdraw(amount)
self.mutex.release() #2
The synchronized account puts both deposit and withdrawal behind the same Python Lock, so a thread holds the mutex while it accesses the balance. section 8.4
Protect every critical section that accesses the shared balance with the same mutex; a thread trying to acquire an occupied lock waits. Other approaches include atomic operations and message passing. Synchronization has a cost, so avoiding shared mutable resources where possible is also worth considering. section 8.4 section 8.7



















