Threads & Concurrency

the read-modify-write hazard

Here is the single most important concrete example of why concurrency goes wrong. The innocent-looking statement count++ is not one action - it is three: read the current value of count from memory, add one to it, and write the new value back. We call this a read-modify-write. It looks indivisible in your source code, but underneath it is three separate steps, and the operating system can switch to another thread in between any of them. That gap is the hazard.

Walk through the failure with two threads, A and B, both doing count++ when count is 5. A reads 5. Before A writes back, the OS switches to B. B reads 5 (the write has not happened yet), adds one to get 6, and writes 6. Now the OS switches back to A, which still holds its old read of 5, adds one to get 6, and writes 6. Two increments happened, but count went from 5 to 6, not 7. One update was silently lost - a 'lost update'. The reason is that A's read-modify-write and B's read-modify-write interleaved instead of each completing as a whole. Nothing in the source said 'do not interrupt me here', so the OS was free to interleave at the worst possible moment.

Why this matters and how it is fixed: read-modify-write is the seed of countless concurrency bugs, because so many natural operations are secretly read-modify-write - incrementing a counter, appending to a shared list, updating a balance, toggling a flag. The fix is to make the three steps happen as one indivisible unit. Two standard ways: wrap them in a critical section guarded by a mutex (so only one thread does the read-modify-write at a time), or use an atomic operation, a special hardware-supported instruction that performs read-modify-write as a single uninterruptible step (the subject of Field n). The key realization is that 'one line of C' does not mean 'one step on the machine', and the machine's steps are where threads collide.

On many CPUs, count++ compiles to roughly three instructions: load count into a register, increment the register, store the register back to count. A thread switch between the load and the store is all it takes to lose an update.

count++ is read-modify-write: three steps, interruptible between any two, so two threads can lose an update.

Declaring the variable volatile does NOT make count++ safe - volatile prevents certain compiler optimizations but does not make the read-modify-write atomic. You need a mutex or a real atomic operation, not volatile.

Also called
why count++ is not atomicRMW hazard非原子的遞增