Question presented to candidate: "What is a race condition? Show me one in Python β and explain why the GIL does not save you, and how a lock fixes it."
What a strong answer should cover:
- A race condition: the result depends on the timing or interleaving of concurrent tasks that share state; classic forms are lost updates (read-modify-write) and check-then-act.
counter += 1is several bytecode steps (load, add, store); a thread switch between the load and the store loses an update. The GIL makes single bytecodes safe for the interpreter, not your multi-step operation.- A lock (
threading.Lock) makes a critical section mutually exclusive: only one thread at a time runs it, so read-modify-write becomes indivisible to other threads that use the same lock. - Use
with lock:so the lock is released even on exceptions; keep critical sections small; every access to the shared data must use the same lock. - Races are timing-dependent and can hide: a tiny
+= 1loop may lose nothing on a given CPython version, yet it is still wrong, and the free-threaded build removes the GIL entirely. - Alternatives: avoid sharing (queues, message passing, per-thread results),
queue.Queue, immutable data; asyncio has races too, atawaitpoints.
Code / implementation expected: Yes β a shared counter or bank balance that loses updates without a lock and is correct with one.