Threads & Concurrency

interleaving of operations

When two threads run on one core, the operating system does not run all of thread A then all of thread B. It runs a few steps of A, switches, runs a few steps of B, switches back, and so on. The combined sequence of steps that actually executes - A's and B's operations shuffled together in time - is called an interleaving. Picture two decks of cards being riffle-shuffled into one pile: each card keeps its own order within its deck, but the two decks end up woven together in some order you did not choose.

The crucial point is that the OS may interleave at almost any boundary between machine instructions, and you usually have no control over where. So for any two threads there are many possible interleavings, and your program must be correct under every one of them. If even a single bad interleaving produces a wrong answer, you have a bug - and the OS is free to pick exactly that interleaving on the day your service is busiest. This is why reasoning about concurrent code is hard: you cannot just trace one run; you must consider all the ways the steps could be woven together. The read-modify-write failure is precisely one specific bad interleaving of two count++ operations.

Why this lens is so useful: 'is this code correct?' becomes 'is this code correct under every interleaving?', which immediately exposes the danger of shared mutable state. It also explains why the cure is to shrink the set of interleavings that matter: when you wrap a critical section in a lock, you forbid the interleavings where two threads are inside it at once, leaving only the safe ones. And it explains why concurrency bugs are so hard to reproduce - the specific harmful interleaving may occur only once in millions of runs, depending on timing you cannot dictate.

Thread A's steps a1,a2,a3 and thread B's steps b1,b2,b3 might run as a1,a2,a3,b1,b2,b3 (no problem) or as a1,b1,b2,b3,a2,a3 (B sneaks fully in between A's first and second step). Different interleavings, possibly different results - and you do not get to choose which one happens.

Many interleavings are possible; correct code must survive all of them.

Adding sleep() or print statements changes the timing and so changes which interleavings you observe - which is why a bug can vanish the moment you try to watch it (a heisenbug). The bug is still there; you just made the harmful interleaving rarer.

Also called
operation interleavinginstruction interleaving交錯