Why two threads adding 1 to the same counter can lose a count
Adding 1 to a shared counter is really three CPU steps: load the value, add 1, store it back. If two threads interleave those steps, one update can vanish. This is a race condition: the result depends on timing.
Say the counter is 5. Thread A loads 5, then thread B loads 5 too, each adds 1, and both store 6. Two increments ran but the counter only went up by one.
A mutex (mutual exclusion lock) can be held by only one thread at a time. A thread calls lock() before touching the counter and unlock() after; others calling lock() wait. The code in between is the critical section.
Operating systems · 6 of 92 / 3
Locks and mutexes
Real-life use case
How Linux mutexes skip the kernel until two threads collide
On Linux, the standard pthread mutex is built on a futex (fast user-space mutex). When the lock is free, locking and unlocking are each one atomic CPU instruction on a shared integer, with no system call.
Only if the lock is already held does a thread make the futex system call, asking the kernel to put it to sleep; unlock then asks the kernel to wake it. Since most locks are usually free, the kernel is rarely involved.
CPython's global interpreter lock (GIL) is another well-known mutex: a single lock that, in standard builds, lets only one thread run Python bytecode at a time, which protects the interpreter's internal objects.
Operating systems · 6 of 93 / 3
Locks and mutexes
How it helps
The CPU checks and grabs the lock in one step nobody can split
Plain code can't build a lock: with 'if flag is 0, set it to 1', two threads can both see 0 before either writes. Real locks use an atomic instruction like test-and-set, which reads and writes the flag in one unsplittable step.
The hardware gives one core exclusive ownership of the flag's cache line for the whole read-and-write, so no other core can slip in. Whoever gets back 0 owns the lock; the rest see 1 and spin (retry in a loop) or sleep.
Unlocking writes 0 back, and the atomic instruction plus memory ordering rules also make the holder's writes to the counter visible before the next thread enters.
What it’s forLets exactly one thread take a lock, even when many try at once.
TAS(flag): old ← flag; flag ← 1; return old (one indivisible step)
flag
the lock word in memory: 0 = free, 1 = held
TAS
test-and-set, an atomic CPU instruction
old
the flag's value just before this call; 0 means you got the lock
What it givesOnly the thread whose call returned 0 enters; the rest see 1 and wait.
flag = 0. Thread A: TAS returns 0, flag = 1 → A enters.
Thread B: TAS returns 1, flag stays 1 → B waits.
A unlocks (flag = 0); B's next TAS returns 0 → B enters.