Why critical sections matter

Threads share an address space, making shared data convenient but exposing operations to interference. Incrementing an ordinary counter can involve separate reads, calculations, and writes. If two threads read the same old value before either writes its result, their updates may leave only one increment. A single processor does not eliminate the problem: switching threads can produce the same harmful interleaving.

A critical section is a region whose interactions require control. Mutual exclusion does not mean that the entire computer must execute only one thread. It means that competing executions cannot simultaneously occupy the relevant critical sections for the same protected resource. The restriction removes particular interleavings rather than all concurrency.

References: [2] [1]

Protecting shared state consistently

A typical protocol acquires a common lock before accessing protected state and releases it afterward. The protected region must cover the operation that needs to remain consistent, not merely its final write. Otherwise an earlier decision can become stale before the update occurs.

A lock does not automatically erect an access barrier around data. Every conflicting access must follow the chosen synchronization protocol. A path that ignores the lock, or uses an unrelated lock, can undermine the protection. Conversely, independent structures can use separate locks to preserve concurrency. Choosing the scope of protection is consequently part of program design, not something the lock object determines by itself.

Waiting for a condition while retaining its mutex can block the thread that must change it. Rust Condvar::wait atomically releases the associated mutex and waits, reacquiring it before return. The caller rechecks the predicate while holding the lock, normally in a loop because wakeups may be spurious. Notifications are not buffered as future permits. The mutex protects predicate checks and updates; condition waiting lets another participant act within the complete protocol.

References: [1] [2] [4]

Interfaces and implementations

In the POSIX interface, pthread_mutex_lock blocks when another thread owns the mutex and returns ownership when acquisition succeeds. pthread_mutex_trylock instead returns without waiting if it cannot acquire the mutex immediately. Recursive mutexes allow repeated acquisition by their owner and use a count to determine when release becomes effective; that behavior must not be assumed for every mutex type.

Implementations may use atomic operations and operating-system assistance. Testing an ordinary availability flag and setting it in a separate step leaves a window in which two contenders can both proceed. A spin lock waits by repeatedly attempting acquisition, potentially consuming processor time. Mutual exclusion specifies the required property, not one mandatory waiting mechanism.

References: [3] [1]

Exclusion is not fairness

Exclusion asks whether conflicting entrants can overlap. Fairness and freedom from starvation concern whether a waiting contender gets an opportunity to proceed. A lock can always exclude simultaneous owners while repeatedly allowing other contenders to win. The simple test-and-set spin lock illustrates why exclusion alone is insufficient to infer fairness.

POSIX specifies that scheduling policy determines which blocked thread acquires a mutex after release; this is not an unconditional first-in, first-out promise. Applications must also interpret return values correctly. A robust mutex can report that a previous owner died while leaving protected state inconsistent. Recovering that state remains an application responsibility: ownership of the lock is not proof that the data are correct.

References: [1] [3]