Race conditions and locks
Threads share memory. When two of them read, decide and write the same data, their steps can interleave, and one update overwrites the other. That is a race condition.
import threading
class Counter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()
def add(self):
with self.lock:
self.value += 1
c = Counter()
threads = [threading.Thread(target=c.add)
for _ in range(50)]
for t in threads:
t.start()
for t in threads:
t.join()
print(c.value) # 50Even self.value += 1 is several steps: read, add, store, and a thread can be paused between them. A Lock lets one thread at a time into the block. with self.lock: acquires it and always releases it, even after an exception.
Check, then act
The worst races hide between a check and the action it guards: two threads both see enough money, then both take it. Hold the lock across the check and the change together, so no other thread can slip in between.
Your task
Fix Account so that deposit(amount) and withdraw(amount) are safe to call from many threads at once, using self.lock, and keep the pause in withdraw, which stands in for a slow database call. withdraw subtracts and returns True only if the balance covers amount; otherwise it returns False.
The explanation opens here once every check passes.