Threads & Concurrency

a race condition

A race condition is a bug where the program's correctness depends on the timing or ordering of events that you do not actually control. Two threads 'race' toward the same resource, and which one gets there first decides the outcome - sometimes right, sometimes wrong, depending on luck. The everyday picture: two people reach for the last cookie at the same instant. Whether you get the cookie, the other person gets it, or you bump hands and it falls on the floor depends entirely on who moves a fraction of a second sooner.

Concretely: a race condition exists whenever the result of running threads can change depending on the order in which their operations happen to interleave, and the program assumed a particular order that is not guaranteed. The classic case is two threads checking a condition and then acting on it - 'is the file there? no, create it' - where both check, both find it absent, and both try to create. The flaw is not in any single line; each line is fine. The flaw is the gap between the check and the act, during which another thread can sneak in and invalidate what the first thread just decided. Because the operating system is free to switch threads at almost any point, that gap is real and exploitable.

Why race conditions are so insidious: they are timing-dependent, so they often pass every test, run fine for months, then fail rarely and unpredictably under heavy load - exactly when you can least afford it and least easily reproduce it. They are a broader category than data races (next entry): a race condition is any correctness-depends-on-timing bug, even one with no low-level memory conflict, such as a 'check then act' that uses perfectly safe individual operations but in an order that can be interrupted. The cure is to make the timing not matter - to make the relevant sequence of steps happen as one indivisible unit, usually with synchronization.

Two threads both run: if (balance >= 100) balance -= 100;. Each reads balance as 100 and passes the check before either subtracts. Both then subtract, and balance ends at -100 - an overdraft that neither check should have allowed. The check and the subtraction were not done as one indivisible step.

A 'check then act' race: the gap between checking and acting lets another thread change the world.

Not every race condition is a data race, and not every data race causes a visibly wrong result every time - that is why they are hard. 'It passed the tests' proves nothing here; a race can hide for a long time and still be lurking.

Also called
racetiming bug競態條件