Async Read-Write LockLoading saved progress…

Async Read-Write Lock

A read-write lock lets any number of readers share a resource at the same time, but gives a writer exclusive access — no other readers, no other writers — for as long as it holds the lock. It is the standard tool for a resource that is read often and written rarely: reads never conflict with each other, so forcing them to run one at a time (as a plain mutex would) wastes throughput. The catch is fairness. If readers keep arriving and you always let them in, a writer can wait forever. This question is about getting the sharing right and keeping the writer from starving.

Implement asyncRwlock(). It returns a lock with two methods. acquireRead() and acquireWrite() each return a Promise that resolves to a release function once the caller is allowed to proceed; the caller does its work, then calls release() to hand the lock back.

Signature

function asyncRwlock(): {
  // resolves to release() once a read slot is granted (many may hold at once)
  acquireRead(): Promise<() => void>;
  // resolves to release() once the lock is held exclusively (no one else holds)
  acquireWrite(): Promise<() => void>;
};

Examples

const lock = asyncRwlock();

const r1 = await lock.acquireRead(); // granted right away
const r2 = await lock.acquireRead(); // also granted — readers do not block readers
// ...both read the shared value concurrently...
r1(); // release
r2(); // release
const lock = asyncRwlock();

const reader = await lock.acquireRead();  // a reader is holding
const writing = lock.acquireWrite();      // writer must wait for the reader
const late = lock.acquireRead();          // arrives AFTER the writer -> queues behind it

reader();                 // reader leaves -> the WRITER runs next, not the late reader
const release = await writing;
release();                // writer leaves -> only now does the late reader run

Notes

  • Many readers, one writer — any number of readers may hold the lock together, but a writer holds it alone, with zero readers and no other writer present.
  • No writer starvation — once a writer is waiting, readers that arrive later must queue behind it rather than joining the batch of readers currently holding the lock.
  • Each acquire owns its release — every call to acquireRead / acquireWrite resolves to its own release function; calling it frees only that one hold.
  • Wake the right waiters on release — when the lock frees up, wake the next run of readers, or the single next writer, in first-in-first-out order.
  • Single-threaded, not parallel — JavaScript runs one thing at a time; concurrent readers means their async work overlaps in time, not that it runs on multiple cores.
  • Out of scope — you do not need read-to-write upgrades, priority tiers beyond FIFO, timeouts, or cancellation. Assume every holder eventually calls release().

FAQ

How is a read-write lock different from a plain mutex?
A mutex allows exactly one holder at any time. A read-write lock relaxes that for readers: any number can hold it concurrently and it only demands exclusivity for a writer, which raises throughput on read-heavy workloads where reads never conflict with each other.
What exactly does 'no writer starvation' mean?
It means a waiting writer cannot be blocked forever by a steady stream of readers. Once a writer is queued, newly arriving readers line up behind it instead of joining the active read batch, so the writer is guaranteed to run once the current readers drain.
Are the concurrent readers actually running in parallel?
No. JavaScript executes on a single thread, so concurrent readers just means their asynchronous critical sections overlap in time, not that code runs on multiple cores. The lock coordinates who is allowed to proceed, not how many CPUs run.
Loading editor…