Threads, Races, and the lock Keyword
What a race condition is, why ++ isn't atomic, and how lock and Interlocked make shared state safe.
Concurrency vs parallelism, threads vs pool
CONCURRENCY is about structure (dealing with many things at once);
PARALLELISM is about execution (doing many things literally simultaneously).
A Thread is an OS execution lane; the ThreadPool reuses a pool of them so
you don't pay thread-creation costs per work item โ Task.Run queues onto
the pool (Module 9's tasks ran there).
The race: read-modify-write is three steps
_count++ compiles to read, add, write. Two threads interleaving can both
read 5 and both write 6 โ one increment vanishes:
int v = _count; // thread A reads 5
// ...thread B reads 5, writes 6...
_count = v + 1; // thread A writes 6 โ B's increment is gone
Races are intermittent and machine-dependent โ the nastiest bug class. Any shared mutable state touched by two threads without synchronization is a race by definition, even if tests never catch it.
lock: mutual exclusion
private readonly object _gate = new();
lock (_gate)
{
_count = _count + 1; // only one thread inside at a time
}
lock is Monitor.Enter/Exit sugar. Rules: lock on a PRIVATE readonly
object (locking this or a Type lets strangers block you); keep the
critical section SMALL; every thread that touches the state must use the
SAME gate โ one unsynchronized accessor reopens the race.
Interlocked: atomic micro-operations
For single-variable updates, Interlocked.Increment/Decrement/Add/Exchange/ CompareExchange make read-modify-write atomic without a lock:
Interlocked.Increment(ref _count); // exact under any contention
Prefer Interlocked for counters; lock for multi-step invariants.
Visibility (concept)
Each thread may cache field values; without synchronization a thread can see
stale data. lock and Interlocked both imply memory barriers โ writes
become visible on exit. Don't sprinkle volatile; synchronize properly and
visibility follows.
Check your understanding
- Why can
++lose updates? (Read-modify-write interleaves between threads.) - What's wrong with
lock(this)? (External code can lock the same object โ deadlock/blocking risk.)