Detecting Race Conditions
16 min
Rather than staring at an interleaving and reasoning about it by hand,
you can check correctness mechanically: if N increments happen, the
final value should be exactly initial_value + N — no more, no less.
Any interleaving where the actual result falls short reveals a lost
update, definitively, without needing to trace through the schedule
step by step.
def is_race_safe(schedule, initial_value=0):
final = simulate_race(schedule, initial_value)
num_writes = sum(1 for _, step in schedule if step == "write")
return final == initial_value + num_writes
This 'expected count vs. actual result' comparison is a general debugging technique, not just for concurrency -- it's the same idea as a checksum: derive an independently-computable expected value, then check the real system against it.
Using the provided `simulate_race` from the previous module, write `is_race_safe(schedule, initial_value=0)`: return True if the final value equals `initial_value` plus the number of `'write'` steps in the schedule (i.e. no update was lost), False otherwise.
What makes `is_race_safe` a useful way to think about race-condition testing?
You can mechanically detect whether a specific thread interleaving lost an update, by comparing the actual result against the mathematically expected one.