skip to content

questions

5

What is a readers-writer lock, how do its shared and exclusive modes interact, and what must be true of a workload for it to beat an ordinary mutex?

level: juniorimportance: must knowfreq 62%

answer

  1. shared many, exclusive one
  2. read/read compatible; anything with write is not
  3. reader count = one shared cache line
  4. needs read-mostly plus long sections
  5. shared mode does not enforce read-only

basics

~20 s

It has two modes: many threads may hold it in shared (read) mode at once, but exclusive (write) mode admits one holder and excludes all readers. It only beats a plain mutex when reads greatly outnumber writes and critical sections are long enough to repay its higher bookkeeping cost.

solid answer

~50 s

A readers-writer lock separates two access intents. **Shared mode** may be held by any number of threads simultaneously — safe because concurrent readers cannot interfere. **Exclusive mode** may be held by exactly one thread and is incompatible with any shared holder. The compatibility rule is: read/read compatible; read/write incompatible; write/write incompatible. The payoff is parallelism among readers, which a mutex forbids. The price is that the lock now maintains more state — a reader count or per-reader bookkeeping — so acquiring in shared mode costs more than acquiring a mutex, and every reader must write to that shared state, bouncing a cache line between cores. So it pays only when the workload is genuinely read-mostly and critical sections are long enough that the extra acquisition cost is amortised. For very short reads on a highly parallel machine, a plain mutex — or an immutable snapshot — is frequently faster than a readers-writer lock.

go deeper

for a junior

Give the two modes and the compatibility rule, and say it suits read-mostly data.

for a middle

Add the cost side — the shared reader counter, two atomics per read — and the conditions under which it actually pays.

for a senior

Discuss cache-line contention on the reader state, the read-only obligation being unenforced, and when a snapshot or sharding beats the lock outright.

for a principal

Treat mode separation as one point on a spectrum from mutex to immutable snapshot to per-core state, and choose based on measured read/write ratio, section length, and core count rather than on the label 'read-heavy'.

## The idea A plain mutex treats all access as conflicting. But two threads that only *read* shared state cannot corrupt it or observe each other's partial updates — the conflict only arises with a writer. A **readers-writer lock** (also shared/exclusive lock) encodes that distinction directly. It offers two acquisition modes: | Requested \ Held | nothing | shared | exclusive | |---|---|---|---| | **shared** | grant | grant | block | | **exclusive** | grant | block | block | The caller declares its intent. Readers take shared mode and proceed together; a writer takes exclusive mode and waits until every reader has left, then locks everyone out for the duration of its update. ## What it costs A mutex, uncontended, is roughly one atomic read-modify-write plus one store to release. A readers-writer lock must track *how many* readers are inside so the last one out can release the lock and let a waiting writer in. That means: - Shared acquire and release are each an atomic update to a shared counter — two atomic operations instead of one for a read. - That counter lives on one cache line, and **every reader on every core writes to it**. Under high read concurrency this line ping-pongs between cores, and the lock itself becomes the contention point even though the readers never actually conflict logically. - The state machine (readers present, writer waiting, writer active) is more complex, so implementations tend to have longer fast paths. Hence the standard rule of thumb: a readers-writer lock wins when reads dominate heavily (often quoted as roughly 90% or more of acquisitions) *and* critical sections are long enough — microseconds, not nanoseconds — for the parallelism to outweigh the bookkeeping. For nanosecond-scale reads, the counter contention alone can make it slower than the mutex it replaced. ## Correctness obligations - **Shared means read-only, and the lock cannot enforce it.** Nothing stops a thread from mutating state while holding shared mode; if it does, you have a data race among the concurrent shared holders, and it will be subtle because 'the code took the lock'. - **Do not leak references.** Returning a mutable internal object to a caller who then uses it after releasing the read lock defeats the whole scheme. Return copies, immutable views, or perform the work inside the critical section. - **Consistency spans the whole section.** Two separate read acquisitions can straddle a writer, so any invariant across two reads must be held in one shared acquisition. - **Writers still need the same discipline as with a mutex**: keep the exclusive section short and never block on I/O inside it, since it excludes every reader in the system. ## Where it genuinely fits Good fits are read-mostly reference data with occasional refresh: routing or configuration tables, permission and feature-flag caches, in-memory indexes rebuilt periodically, metadata that many request threads consult and a background task updates. In each, reads are frequent and non-trivial (a lookup plus some traversal), and writes are rare. Poor fits are counters and tiny fields (the read is shorter than the lock overhead — use an atomic), write-heavy structures (the exclusive mode dominates and you have paid extra for nothing), and cases where the data is small enough to publish as an immutable snapshot that readers take with no lock at all. ## Fairness enters immediately One question the compatibility rule does not answer: when a writer is waiting and a *new* reader arrives while readers are still inside, is the newcomer admitted? Admitting it maximises read throughput but can keep the writer waiting indefinitely; blocking it protects the writer at some cost to readers. That policy choice — reader-preference versus writer-preference versus fair — is a defining property of any real readers-writer lock, and you should know which one you are using before deploying it under load.

  • You replaced a mutex with a readers-writer lock on a 90%-read workload and throughput got worse. What plausibly happened?
    The critical sections were probably too short to amortise the extra cost: a shared acquire and release are two atomic updates to one shared reader counter, so with many cores that single cache line bounces between them and becomes the bottleneck. The readers were contending on the lock's own state even though they never conflicted on the data. Options are to go back to the mutex, publish an immutable snapshot readers can use with no lock, or shard the data so both locks and readers spread out.
  • Does holding the lock in shared mode prevent a thread from modifying the protected data?
    No — the mode is a declaration of intent, not an enforced permission; the lock has no idea what your code touches. If a thread mutates while holding shared mode, it races with every concurrent shared holder, and the bug is especially deceptive because the code visibly takes a lock. Enforcement has to come from design: expose only immutable or copied views to read paths.

A whiteboard in a meeting room: any number of people can read it at once, but the person rewriting it needs the room to themselves so nobody copies down a half-erased diagram.

saying these in an interview costs you the question

  • Assumes a readers-writer lock is always faster than a mutex because reads are common
  • Believes shared mode physically prevents writes
  • Uses it around a single field or counter where an atomic would do
  • Returns a mutable internal reference from a read section and uses it after release
  • Cannot state the read/read compatibility rule

context

open as a page

How can a writer be starved when a lock has shared and exclusive modes, and what admission policies exist to prevent it?

level: middleimportance: must knowfreq 54%

basics

~20 s

Under reader preference, new readers join while readers are already inside, so if reads arrive faster than they finish the reader count never reaches zero and a waiting writer never runs. Writer-preference blocks arriving readers once a writer is queued; fair or phase-fair policies queue both by arrival and alternate between them.

open as a page

How does an optimistic read with validation (the sequence-lock or seqlock idea) work, and what constraints does it place on the reader's code?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The reader takes no lock: it records a version counter, reads the data, then re-reads the counter. If it changed, or was odd (a write in progress), the read is discarded and retried. Readers must therefore tolerate momentarily inconsistent data — no side effects, no dereferencing possibly-freed pointers, no unbounded work before validation.

open as a page

Why is upgrading a shared (read) lock hold to exclusive (write) mode a deadlock hazard, why is downgrading the other way safe, and how do you restructure code that seems to need an upgrade?

level: seniorimportance: should knowfreq 44%

basics

~20 s

If two readers both try to upgrade, each waits for the other to release its shared hold, so neither can ever get exclusive mode — a deadlock with no cycle of distinct locks. Downgrading is safe because the holder already excludes everyone and simply relaxes. Restructure by releasing, re-acquiring exclusively, and re-validating, or by using a single-holder upgradeable mode.

open as a page

You are told a data structure is read-mostly and should therefore be guarded by a shared/exclusive lock. How would you decide whether that is actually the right mechanism, and what alternatives would you weigh?

level: principalimportance: should knowfreq 42%

basics

~20 s

Measure first: read/write ratio, critical-section length, and core count. Mode separation only pays if reads dominate and sections are long enough to amortise the reader bookkeeping. Otherwise weigh a plain mutex, an immutable snapshot swapped atomically, sharded state, per-thread state with aggregation, or optimistic validated reads.

open as a page