Skillversitas

Race Conditions

A race condition happens when the correctness of a result depends on the relative timing of concurrent operations. Two threads read the same value, both compute from it, and both write back - and one update silently disappears. Race conditions are the most common concurrency bug because they are nondeterministic: the test passes ten times and fails once, in production.

The Broken Counter

import threading

counter = 0

def increment():
    global counter
    for _ in range(100_000):
        counter += 1   # NOT atomic: read, add, write

threads = [threading.Thread(target=increment) for _ in range(4)]
[t.start() for t in threads]
[t.join() for t in threads]
print(counter)  # expected 400000 - often less, e.g. 374512

Why It Fails

The statement counter += 1 is three bytecodes: load the value, add one, store the result. Between the load and the store, the interpreter may switch to another thread. If two threads both load 5, both store 6, and the net effect of two increments is one. The more threads and the more iterations, the more updates get lost - but the exact number changes every run, which is what makes these bugs hard to reproduce.

Other Shapes of the Same Bug

  • Check-then-act: if key not in cache: cache[key] = load() - two threads both miss and both load.
  • Double initialization: two threads see a None singleton and both construct it.
  • Ordered output: threads append to a list in an order the algorithm depends on.

Detecting Race Conditions

Run the suspicious code in a loop and watch for a wrong total; add a lock and see if the result stabilizes; or use python -X dev (development mode) plus stress tests in CI. If adding a lock makes the bug disappear, you had a race - and you now know which critical section to protect.

Summary

Whenever two threads can reach the same mutable state, ask what happens if their instructions interleave. +=, if-not-then-set, and append-then-read are the classic shapes. The fix is always the same family: make the read-modify-write sequence atomic with a lock or a queue.