"Python threading Lock Example: Kill Race Conditions"
"A python threading lock example with the race condition reproduced before it's fixed — then Lock vs RLock vs Semaphore, deadlock-free ordering, and a decision table."
After this guide, threads will never silently corrupt your shared state again. You'll reproduce a real race condition, fix it with one with statement, and know exactly when to reach for Lock , RLock , or Semaphore — the three synchronization primitives that cover nearly every threading bug you'll meet in backend code. This python threading lock example starts with the broken version on purpose: seeing the corruption first is what makes the fix stick. Last updated: 2026-10-05 Watch a race condition happen Four threads, each incrementing a counter 200,000 times. Expected total: 800,000. Run it: import threading counter = 0 def bump(): global counter for _ in range(200_000): counter += 1 threads = [threading.Thread(target=bump) for _ in range(4)] for t in threads: t.start() for t in threads: t.join() print("counter:", counter) Output (yours will vary — run it three times and watch it change): counter: 412637 412,637 instead of 800,000. Nearly half the increments vanished. Here's why: counter += 1 is three operations — read the value, add one, write it back. Two threads can interleave between those steps: Thread A reads 100 Thread B reads 100 <- same stale value Thread A writes 101 Thread B writes 101 <- A's increment is lost Two increments happened; the counter moved by one. This is a *lost update*, the classic race condition. The GIL doesn't save you — it switches threads between bytecodes, and the read/add/write sequence is not atomic. Any shared mutable state touched by multiple threads has this disease. Now the cure. What you'll build and learn Reproduce a race condition and understand lost updates at the bytecode level Write the canonical python threading lock example using the with statement Know when a plain Lock deadlocks on itself —…