Facebook Pixel

Reader-Writer Locks in Process Synchronization

Most synchronization problems treat all processes the same: everyone competes for exclusive access and takes turns one at a time. But in reality, not all access to shared data is equal.

Reading something does not change it, so two readers going at the same time is completely harmless. Writing is a different story. A writer modifying shared data while someone else is reading it, or while another writer is also modifying it, can cause serious problems. The reader-writer problem is about handling this distinction properly.

The Core Rules

You have a shared resource, say a database or a file. Some processes only read from it. Others need to write to it. The rules are:

  • Multiple readers can access the data at the same time without any issue since they are not changing anything.
  • A writer needs exclusive access. While a writer is working, nobody else, whether reader or another writer, should be allowed in.
  • If a writer is in the middle of an update and a reader reads at the same time, the reader might get half-old and half-new data, which is inconsistent and potentially wrong.
  • If two writers go at the same time, their changes can conflict and corrupt the data entirely.
text
Allowed combinations:

  Reader + Reader   -> SAFE (no data changes)
  Reader + Writer   -> UNSAFE (inconsistent read)
  Writer + Writer   -> UNSAFE (data corruption)
  Writer alone      -> SAFE (exclusive access)

Access Matrix:
              Reader    Writer
  Reader       OK        BLOCK
  Writer      BLOCK      BLOCK

Two Approaches to Priority

ApproachHow It WorksAdvantageRisk
Reader PreferenceAs long as at least one reader is active, writers wait. New readers can jump in even if a writer is already waiting.Great for read-heavy systems. Maximum read parallelism.Writers can starve if readers keep arriving continuously.
Writer PreferenceOnce a writer is ready, it gets priority. Readers wait until the writer is done.Writers are never stuck behind an endless stream of readers.Can slow down reading throughput. Readers may experience delays.

Solving It With Semaphores

The classic semaphore-based solution (reader preference) uses two semaphores and one integer variable:

  • write_sem (initialized to 1): Controls access to the shared resource for writing. Both readers and writers use it, but in different ways.
  • mutex_sem (initialized to 1): Protects the readcount variable so that only one reader updates it at a time.
  • readcount (integer, starts at 0): Tracks how many readers are currently active.

Writer Logic

A writer simply waits on the write semaphore. If it is free, the writer gets in and does its work. Nobody else can enter while the write semaphore is held. When done, the writer signals it.

text
Writer:
  write_sem.wait()        // Request exclusive access
  // --- Write to shared resource ---
  write_sem.signal()      // Release access

Reader Logic

Readers are more involved. The first reader to arrive locks out writers. The last reader to leave lets writers back in. In between, any number of readers can enter freely.

text
Reader (Entry):
  mutex_sem.wait()        // Protect readcount
  readcount++
  if (readcount == 1)     // First reader?
      write_sem.wait()    // Block writers
  mutex_sem.signal()      // Let other readers update readcount

  // --- Read from shared resource ---

Reader (Exit):
  mutex_sem.wait()        // Protect readcount
  readcount--
  if (readcount == 0)     // Last reader?
      write_sem.signal()  // Let writers in
  mutex_sem.signal()
Why Only the First and Last Reader Touch write_sem
If every reader individually acquired and released write_sem, readers would be forced into sequential access, defeating the entire purpose. Instead, only the first reader locks out writers, and only the last reader lets them back in. All readers in between enter freely. This is what enables true read parallelism.

Implementation in C++

cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <semaphore>
#include <chrono>
#include <vector>

std::counting_semaphore<1> write_sem(1);  // Controls write access
std::counting_semaphore<1> mutex_sem(1);  // Protects readcount
int readcount = 0;                         // Active readers
int shared_data = 0;                       // The shared resource

void writer(int id) {
    for (int i = 0; i < 3; i++) {
        std::cout << "Writer " << id << " waiting to write..." << std::endl;

        write_sem.acquire();               // Exclusive access

        shared_data++;
        std::cout << "Writer " << id << " writing. Data is now: "
                  << shared_data << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        std::cout << "Writer " << id << " done writing." << std::endl;

        write_sem.release();               // Release access
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

void reader(int id) {
    for (int i = 0; i < 3; i++) {
        std::cout << "Reader " << id << " waiting to read..." << std::endl;

        // Entry: update readcount safely
        mutex_sem.acquire();
        readcount++;
        if (readcount == 1)
            write_sem.acquire();           // First reader blocks writers
        mutex_sem.release();

        // Read the shared resource
        std::cout << "Reader " << id << " reading. Data is: "
                  << shared_data << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(300));
        std::cout << "Reader " << id << " done reading." << std::endl;

        // Exit: update readcount safely
        mutex_sem.acquire();
        readcount--;
        if (readcount == 0)
            write_sem.release();           // Last reader unblocks writers
        mutex_sem.release();

        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

int main() {
    std::cout << "Reader-Writer Problem Simulation" << std::endl;
    std::cout << "Initial shared data: " << shared_data << std::endl;
    std::cout << "-----------------------------------" << std::endl;

    std::thread w1(writer, 1);
    std::thread r1(reader, 1);
    std::thread r2(reader, 2);
    std::thread r3(reader, 3);

    w1.join(); r1.join(); r2.join(); r3.join();

    std::cout << "-----------------------------------" << std::endl;
    std::cout << "Final shared data: " << shared_data << std::endl;
    return 0;
}

Output:

text
Reader-Writer Problem Simulation
Initial shared data: 0
-----------------------------------
Writer 1 waiting to write...
Reader 1 waiting to read...
Reader 2 waiting to read...
Reader 3 waiting to read...
Writer 1 writing. Data is now: 1
Writer 1 done writing.
Reader 1 reading. Data is: 1
Reader 2 reading. Data is: 1
Reader 3 reading. Data is: 1
Reader 1 done reading.
Reader 2 done reading.
Reader 3 done reading.
Writer 1 writing. Data is now: 2
...
-----------------------------------
Final shared data: 3

Multiple readers access the data at the same time without blocking each other. The writer waits until all readers are done, then gets exclusive access. Once writing is complete, readers can come back in.

Where This Shows Up in Real Systems

SystemReadersWritersWhy RW Locks Help
DatabasesSELECT queries from many usersUPDATE/DELETE operationsParallel reads massively improve query throughput
File SystemsMultiple programs opening a file for readingA program writing or appending to the filePrevents reading half-written data
Server ConfigHundreds of threads reading the configAdmin pushing a config updateNo thread reads a half-updated configuration
DNS CachesThousands of lookups per secondPeriodic cache refresh from upstreamLookups continue in parallel during normal operation
In-Memory CachesApplication threads reading cached dataBackground thread refreshing stale entriesRead-heavy workloads get near-zero contention

Reader-Writer Lock vs Plain Mutex

A plain mutex would force readers to take turns even though they could safely go at the same time. The reader-writer approach lets you get real parallelism out of read-heavy workloads while still protecting writes properly. For systems where reads vastly outnumber writes, the performance difference can be dramatic.

Reader-Writer Scenario

Question 1 of 1

Test your understanding of concurrent reader-writer access.

Three readers (R1, R2, R3) are currently reading the shared resource. readcount is 3. A writer W1 is waiting to write. Now R1 finishes reading. What happens?
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.