# Read-Write Lock Pattern

## Problem

Multiple threads need to access shared data. Reads are far more frequent than writes. Using a simple mutex serializes all access, even concurrent reads that cannot conflict, degrading throughput.

## Solution

Use a read-write lock (RWLock) that allows unlimited concurrent readers **or** a single exclusive writer. Readers acquire a shared lock; writers acquire an exclusive lock that blocks all other access.

## When to Use

- Read-heavy workloads (caches, configuration stores, lookup tables)
- Data that is read frequently but updated infrequently
- When read throughput matters more than write latency

## When to Avoid

- Write-heavy workloads (readers starve or overhead negates benefit)
- Simple data where an atomic swap suffices
- When lock-free structures (e.g., CAS-based) are practical

## Pseudocode

```
rwlock = ReadWriteLock()

function reader(id):
    rwlock.read_lock()
    value = shared_data
    print("Reader " + id + " read: " + value)
    rwlock.read_unlock()

function writer(id, new_value):
    rwlock.write_lock()
    shared_data = new_value
    print("Writer " + id + " wrote: " + new_value)
    rwlock.write_unlock()
```

## Python

```python
import threading
import time

shared_data: dict = {"value": 0}
rwlock = threading.RLock()
read_count = 0
read_count_lock = threading.Lock()
resource_lock = threading.Lock()

def read_lock():
    global read_count
    with read_count_lock:
        read_count += 1
        if read_count == 1:
            resource_lock.acquire()

def read_unlock():
    global read_count
    with read_count_lock:
        read_count -= 1
        if read_count == 0:
            resource_lock.release()

def reader(reader_id: int) -> None:
    for _ in range(3):
        read_lock()
        val = shared_data["value"]
        print(f"[Reader {reader_id}] Read value: {val}")
        time.sleep(0.05)
        read_unlock()
        time.sleep(0.01)

def writer(writer_id: int) -> None:
    for i in range(3):
        resource_lock.acquire()
        shared_data["value"] = writer_id * 100 + i
        print(f"[Writer {writer_id}] Wrote value: {shared_data['value']}")
        time.sleep(0.1)
        resource_lock.release()
        time.sleep(0.05)

def main() -> None:
    threads = []
    for i in range(3):
        threads.append(threading.Thread(target=reader, args=(i,)))
    for i in range(2):
        threads.append(threading.Thread(target=writer, args=(i,)))

    for t in threads:
        t.start()
    for t in threads:
        t.join()

    print(f"Final value: {shared_data['value']}")

if __name__ == "__main__":
    main()
```

## Go

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	sharedData int
	rwMutex    sync.RWMutex
)

func reader(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 3; i++ {
		rwMutex.RLock()
		fmt.Printf("[Reader %d] Read value: %d\n", id, sharedData)
		time.Sleep(50 * time.Millisecond)
		rwMutex.RUnlock()
		time.Sleep(10 * time.Millisecond)
	}
}

func writer(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 3; i++ {
		rwMutex.Lock()
		sharedData = id*100 + i
		fmt.Printf("[Writer %d] Wrote value: %d\n", id, sharedData)
		time.Sleep(100 * time.Millisecond)
		rwMutex.Unlock()
		time.Sleep(50 * time.Millisecond)
	}
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go reader(i, &wg)
	}
	for i := 0; i < 2; i++ {
		wg.Add(1)
		go writer(i, &wg)
	}

	wg.Wait()
	fmt.Printf("Final value: %d\n", sharedData)
}
```

## JavaScript

```javascript
// Conceptual RWLock for async JavaScript
class ReadWriteLock {
  constructor() {
    this.readers = 0;
    this.writer = false;
    this.waitingWriters = [];
    this.waitingReaders = [];
  }

  async readLock() {
    while (this.writer || this.waitingWriters.length > 0) {
      await new Promise((r) => this.waitingReaders.push(r));
    }
    this.readers++;
  }

  readUnlock() {
    this.readers--;
    if (this.readers === 0 && this.waitingWriters.length > 0) {
      this.waitingWriters.shift()();
    }
  }

  async writeLock() {
    while (this.writer || this.readers > 0) {
      await new Promise((r) => this.waitingWriters.push(r));
    }
    this.writer = true;
  }

  writeUnlock() {
    this.writer = false;
    // Wake all waiting readers first, then one writer
    while (this.waitingReaders.length > 0) {
      this.waitingReaders.shift()();
    }
    if (this.waitingWriters.length > 0) {
      this.waitingWriters.shift()();
    }
  }
}

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

let sharedData = 0;
const rwLock = new ReadWriteLock();

async function reader(id) {
  for (let i = 0; i < 3; i++) {
    await rwLock.readLock();
    console.log(`[Reader ${id}] Read value: ${sharedData}`);
    await sleep(50);
    rwLock.readUnlock();
    await sleep(10);
  }
}

async function writer(id) {
  for (let i = 0; i < 3; i++) {
    await rwLock.writeLock();
    sharedData = id * 100 + i;
    console.log(`[Writer ${id}] Wrote value: ${sharedData}`);
    await sleep(100);
    rwLock.writeUnlock();
    await sleep(50);
  }
}

async function main() {
  const tasks = [];
  for (let i = 0; i < 3; i++) tasks.push(reader(i));
  for (let i = 0; i < 2; i++) tasks.push(writer(i));
  await Promise.all(tasks);
  console.log(`Final value: ${sharedData}`);
}

main();
```

## Related Patterns

- **Monitor** — a monitor (mutex + condition variables) is the lower-level primitive from which read-write locks are built.
- **Immutability** — immutable data eliminates the need for read-write locks entirely; prefer immutability when possible.
