What does a mutual-exclusion lock (a mutex) actually guarantee to the code that uses it, and what does it explicitly not guarantee?
answer
- one holder at a time, per lock instance
- exclusion + memory ordering, both jobs
- lock-to-data is a convention
- no fairness, no deadlock freedom, no composition
- owned: releaser == acquirer
basics
~20 sAt most one thread holds a given mutex, so critical sections guarded by that same lock never overlap, and a holder's writes become visible to the next holder. It promises nothing about acquisition order, fairness, deadlock freedom, or data guarded by another lock.
solid answer
~50 sA mutex enforces **mutual exclusion**: between a successful acquire and the matching release, no other thread can be inside a section guarded by the *same* lock. It does a second job too — releasing and then acquiring the same lock orders memory, so everything the previous holder did before releasing is visible to the next holder. Exclusion plus that ordering is what makes the guarded state correct, not merely non-overlapping. What it does not give: no fairness or FIFO ordering unless the lock is explicitly fair; no protection for state that some code path touches *without* taking the lock, because the lock-to-data association is a convention nobody enforces; no deadlock freedom once two locks are involved; and no composition — two operations that are each atomic under the lock are not jointly atomic unless you hold the lock across both. Nor any performance guarantee: under contention, acquire becomes parking plus a context switch.
code
text · 9 lines// NOT atomic: another thread can insert between the two calls
if (not map.containsAtomic(k)) {
map.putAtomic(k, v)
}
// Atomic: one critical section spans the whole decision
lock.acquire()
try { if (not map.contains(k)) map.put(k, v) }
finally { lock.release() }go deeper
State the core rule: one thread at a time, and only against code using the same lock object.
Add the memory-ordering half and explain why unsynchronised reads of locked data are still broken.
Talk about what the lock does not give — fairness, deadlock freedom, composition — and about hold-time discipline: no I/O or alien calls under a lock.
Frame the lock as a serialization budget: every critical section is a fragment of the program forced to run sequentially, so the design question is how much of the workload you are agreeing to serialize and where that ceiling lands.
## Critical sections and the exclusion rule A **critical section** is a region of code that touches shared mutable state and is correct only if no conflicting region runs at the same instant. A **mutex** is an object with two operations, *acquire* and *release*, and one invariant: at most one thread at a time sits between a successful acquire and its matching release. Everything else about locks is built on that single property. Classically, a correct mutual-exclusion protocol is judged on three properties: 1. **Mutual exclusion (safety)** — never two threads in the critical section at once. 2. **Progress / deadlock-freedom (liveness)** — if the lock is free and threads want it, some thread eventually gets in; threads not competing cannot postpone the decision forever. 3. **Bounded waiting (starvation-freedom)** — a waiting thread is overtaken only a bounded number of times. Real-world mutexes reliably give you (1) and (2). They usually do **not** give (3) unless you ask for a fair lock, because barging — letting a newly arriving thread grab a just-released lock ahead of the queue — is far faster. ## The second job: memory ordering On a multiprocessor, exclusion alone would not be enough. Compilers and CPUs reorder and buffer writes, so thread B could execute after A yet still see A's stale values. Every practical lock therefore also acts as a memory barrier: the release by A *happens before* a subsequent acquire of the *same* lock by B, so all of A's prior writes are visible to B. This is why 'I took the lock but I still saw an old value' is almost always evidence that the writer did not hold that same lock. ## The lock protects a convention, not memory Nothing in hardware or the runtime ties lock `L` to field `x`. The protection exists only because *every* piece of code that reads or writes `x` agrees to hold `L` first. Two consequences follow. Using a different lock object gives zero exclusion — the guard silently disappears. And a single unsynchronised read elsewhere reintroduces a data race even if every write is locked. Documenting the guarding lock per field is the cheap defence. ## What a mutex does not do - **No ordering fairness.** Threads may be served in any order; a hot thread can starve a cold one. - **No deadlock freedom.** Two locks acquired in opposite orders deadlock; the mutex cannot know. - **No composition.** `if (!map.contains(k)) map.put(k, v)` is not atomic just because each call is internally locked — you must hold one lock across both. - **No protection against yourself.** Blocking on I/O, or calling unknown ('alien') code while holding the lock, keeps every other thread out for the duration and invites deadlock. - **No cost-free contention.** An uncontended acquire is roughly one atomic read-modify-write on a cache line — tens of nanoseconds. A contended one parks the thread and costs a context switch, typically microseconds. ## Ownership A mutex is normally **owned**: only the thread that acquired it may release it. That ownership is what makes reentrancy, priority inheritance, and 'illegal unlock' errors definable. It is also the main semantic difference from a counting semaphore, where any thread may signal — which is why a semaphore initialised to one is a lock-shaped tool but not a mutex.
- A field is written only while holding a lock, but one reporting path reads it without the lock. Is that safe?No — it is still a data race. The reader has no ordering edge with the writer, so it may observe a stale value indefinitely, or in languages with unaligned/word-tearing semantics even a partially written value. Correctness requires every access, read as well as write, to use the same lock (or an explicitly atomic read).
- How does a mutex differ from a binary semaphore?A mutex has ownership: only the acquiring thread may release it, which lets the implementation support reentrancy, detect illegal unlocks, and apply priority inheritance. A binary semaphore is just a counter capped at one that any thread may signal, so it is suited to signalling between threads rather than protecting a critical section.
A mutex is the single key to a room. It stops two people entering at once, but it does not decide who gets the key next, does not stop someone climbing in the window, and does not help if a second room needs a second key.
saying these in an interview costs you the question
- Says a lock 'makes the object thread-safe' regardless of how callers use it
- Assumes waiters are granted the lock in FIFO arrival order
- Forgets that reads also need the lock, not just writes
- Thinks locking each method separately makes a sequence of calls atomic
- Ignores the memory-visibility half of what a lock does