- OS - Home
- OS - Overview
- OS - History
- OS - Evolution
- OS - Functions
- OS - Components
- OS - Structure
- OS - Architecture
- OS - Services
- OS - Properties
- Process Management
- Processes in Operating System
- States of a Process
- Process Schedulers
- Process Control Block
- Operations on Processes
- Process Suspension and Process Switching
- Process States and the Machine Cycle
- Inter Process Communication (IPC)
- Context Switching
- Threads
- Types of Threading
- Multi-threading
- System Calls
- Scheduling Algorithms
- Process Scheduling
- Types of Scheduling
- Scheduling Algorithms Overview
- FCFS Scheduling Algorithm
- SJF Scheduling Algorithm
- Round Robin Scheduling Algorithm
- HRRN Scheduling Algorithm
- Priority Scheduling Algorithm
- Multilevel Queue Scheduling
- Lottery Scheduling Algorithm
- Starvation and Aging
- Turn Around Time & Waiting Time
- Burst Time in SJF Scheduling
- Process Synchronization
- Process Synchronization
- Solutions For Process Synchronization
- Hardware-Based Solution
- Software-Based Solution
- Critical Section Problem
- Critical Section Synchronization
- Mutual Exclusion Synchronization
- Mutual Exclusion Using Interrupt Disabling
- Peterson's Algorithm
- Dekker's Algorithm
- Bakery Algorithm
- Semaphores
- Binary Semaphores
- Counting Semaphores
- Mutex
- Turn Variable
- Bounded Buffer Problem
- Reader Writer Locks
- Test and Set Lock
- Monitors
- Sleep and Wake
- Race Condition
- Classical Synchronization Problems
- Dining Philosophers Problem
- Producer Consumer Problem
- Sleeping Barber Problem
- Reader Writer Problem
- OS Deadlock
- Introduction to Deadlock
- Conditions for Deadlock
- Deadlock Handling
- Deadlock Prevention
- Deadlock Avoidance (Banker's Algorithm)
- Deadlock Detection and Recovery
- Deadlock Ignorance
- Resource Allocation Graph
- Livelock
- Memory Management
- Memory Management
- Logical and Physical Address
- Contiguous Memory Allocation
- Non-Contiguous Memory Allocation
- First Fit Algorithm
- Next Fit Algorithm
- Best Fit Algorithm
- Worst Fit Algorithm
- Buffering
- Fragmentation
- Compaction
- Virtual Memory
- Segmentation
- Buddy System
- Slab Allocation
- Overlays
- Free Space Management
- Locality of Reference
- Paging and Page Replacement
- Paging
- Demand Paging
- Page Table
- Page Replacement Algorithms
- Optimal Page Replacement Algorithm
- Belady's Anomaly
- Thrashing
- Storage and File Management
- File Systems
- File Attributes
- Structures of Directory
- Linked Index Allocation
- Indexed Allocation
- Disk Scheduling Algorithms
- FCFS Disk Scheduling
- SSTF Disk Scheduling
- SCAN Disk Scheduling
- LOOK Disk Scheduling
- I/O Systems
- I/O Hardware
- I/O Software
- I/O Programmed
- I/O Interrupt-Initiated
- Direct Memory Access
- OS Types
- OS - Types
- OS - Batch Processing
- OS - Multiprocessing
- OS - Hybrid
- OS - Monolithic
- OS - Zephyr
- OS - Nix
- OS - Linux
- OS - Blackberry
- OS - Garuda
- OS - Tails
- OS - Clustered
- OS - Haiku
- OS - AIX
- OS - Solus
- OS - Tizen
- OS - Bharat
- OS - Fire
- OS - Bliss
- OS - VxWorks
- OS - Embedded
- OS - Single User
- Miscellaneous Topics
- OS - Security
- OS Questions Answers
- OS - Questions Answers
- OS Useful Resources
- OS - Quick Guide
- OS - Useful Resources
- OS - Discussion
Operating System - Reader Writer Problem
The Reader Writer Problem is a synchronization problem between two concurrent processes that share a common resource (like a database or a variable). Read this chapter to understand the problem, its conditions, and how to implement a synchronization mechanisms using semaphores to solve it.
Reader Writer Problem Explained
In the Reader Writer Problem, there are two types of processes: readers and writers, between them there is a shared resource. Readers can read the shared resource at any time without any issues. But for writers, they need exclusive access to the resource to avoid data inconsistency. Means, when a writer is writing to the resource, no other writer or reader are allowed to access it.
Our task is to design a synchronization mechanism that allows multiple readers to read the resource simultaneously, but only one writer can write to the resource at a time. Also no readers can read the resource while a writer is writing to it.
How Data Inconsistency Occurs?
Imagine there are 2 writers (W1 and W2), 2 readers (R1 and R2) and a shared resource (variable X) initialized to 5. Assume that the writers also have the ability to read the variable.
Initial Value of X = 5 W1 reads X (=5) W2 reads X (=5) W1 increments X by 1 (Now, X=6) W2 decrements X by 1 (Now, X=4) W2 writes X (=4) R1 reads X (=4) W1 writes X (=6) R2 reads X (=6) R1's value of X : 4 R2's value of X : 6
This shows how same data can have different meanings for different readers due to concurrent writes by writers. This is called as data inconsistency. Imagine, what could happen in a banking system if two transactions are trying to update the same account balance simultaneously without proper synchronization!!
Solution of the Problem
To solve this problem, we can use semaphores to manage access to the shared resource. We can use the following semaphores −
- mutex − This semaphore is used to provide mutual exclusion when updating the count of active readers.
- writeBlock − This semaphore is used to block writers when there are active readers.
The pseudocode for the reader process is as follows −
void reader() {
wait(mutex);
read_count++;
if (read_count == 1) {
wait(writeBlock); // first reader locks out writers
}
signal(mutex);
// critical section (reading)
read_data();
wait(mutex);
read_count--;
if (read_count == 0) {
signal(writeBlock); // last reader releases writers
}
signal(mutex);
}
The pseudocode for the writer process is as follows −
void writer() {
wait(writeBlock); // acquire exclusive access for writing
// critical section (writing)
write_data();
signal(writeBlock); // release exclusive access
}
Here, the reader process increments the read_count when it starts reading and decrements it when it finishes. This allows multiple readers to read the resource at the same time. The first reader locks the writeBlock semaphore to prevent writers from reading while there are active readers.
Implementation in Programming Languages
Here is a simple implementation of the Reader Writer Problem using semaphores in C++, Python and Java −
Note − The solution uses multithreading to simulate the concurrent readers and writers.
import threading
import time
# Shared variable
X = 5
# Readers-writer synchronization
mutex = threading.Lock() # protects read_count
write_lock = threading.Lock() # exclusive for writers
read_count = 0
def reader(name: str, delay_before=0.0):
global read_count, X
time.sleep(delay_before)
# entry section
with mutex:
read_count += 1
if read_count == 1:
# first reader locks out writers
write_lock.acquire()
# critical section (reading)
print(f"[{name}] READ X = {X}")
# exit section
with mutex:
read_count -= 1
if read_count == 0:
# last reader releases writers
write_lock.release()
def writer(op: str, name: str, delay_before=0.0, delay_between_read_write=0.0):
global X
time.sleep(delay_before)
# Acquire exclusive access for full RMW
with write_lock:
old = X
print(f"[{name}] READ X = {old}")
# simulate some work before writing (allows you to test that others are blocked)
if delay_between_read_write > 0:
time.sleep(delay_between_read_write)
# apply operation
if callable(op):
new = op(old)
elif op == "inc":
new = old + 1
elif op == "dec":
new = old - 1
else:
raise ValueError("Unsupported op")
X = new
print(f"[{name}] WROTE X = {X}")
def demo_synchronized():
global X
X = 5
print("Initial X =", X)
w1 = threading.Thread(target=writer, args=("inc", "W1", 0.0, 0.5))
w2 = threading.Thread(target=writer, args=("dec", "W2", 0.05, 0.0))
r1 = threading.Thread(target=reader, args=("R1", 0.7))
r2 = threading.Thread(target=reader, args=("R2", 1.2))
w1.start()
w2.start()
r1.start()
r2.start()
w1.join()
w2.join()
r1.join()
r2.join()
print("Final X =", X)
if __name__ == "__main__":
demo_synchronized()
The output of the above Python code will look something like this −
Initial X = 5 [W1] READ X = 5 [W1] WROTE X = 6 [W2] READ X = 6 [W2] WROTE X = 5 [R1] READ X = 5 [R2] READ X = 5 Final X = 5
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <string>
#include <vector>
#include <random>
int X = 5;
std::mutex mutex; // protects read_count
std::mutex write_lock; // exclusive for writers; held by first reader to block writers
int read_count = 0;
void reader(const std::string &name, int delay_before_ms = 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(delay_before_ms));
mutex.lock();
read_count++;
if (read_count == 1) {
write_lock.lock();
}
mutex.unlock();
// reading
std::cout << "[" << name << "] READ X = " << X << std::endl;
mutex.lock();
read_count--;
if (read_count == 0) {
write_lock.unlock();
}
mutex.unlock();
}
void writer(const std::string &op, const std::string &name, int delay_before_ms = 0, int delay_between_ms = 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(delay_before_ms));
write_lock.lock();
int old = X;
std::cout << "[" << name << "] READ X = " << old << std::endl;
if (delay_between_ms > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(delay_between_ms));
}
int nw = old;
if (op == "inc") nw = old + 1;
else if (op == "dec") nw = old - 1;
else {
try {
nw = std::stoi(op);
} catch (...) { /* ignore, keep old */ }
}
X = nw;
std::cout << "[" << name << "] WROTE X = " << X << std::endl;
write_lock.unlock();
}
int main() {
X = 5;
std::cout << "Initial X = " << X << std::endl;
std::thread w1(writer, "inc", "W1", 0, 500); // W1 increments, holds lock during RMW
std::thread w2(writer, "dec", "W2", 50, 0); // W2 tries shortly after W1 starts, will wait
std::thread r1(reader, "R1", 700); // R1 reads after some events
std::thread r2(reader, "R2", 1200); // R2 reads later
w1.join();
w2.join();
r1.join();
r2.join();
std::cout << "Final X = " << X << std::endl;
return 0;
}
The output of the above C++ code will look something like this −
Initial X = 5 [W1] READ X = 5 [W1] WROTE X = 6 [W2] READ X = 6 [W2] WROTE X = 5 [R1] READ X = 5 [R2] READ X = 5 Final X = 5
import java.util.concurrent.locks.ReentrantLock;
public class RWDemo {
static int X = 5;
static ReentrantLock mutex = new ReentrantLock(); // protects readCount
static ReentrantLock writeLock = new ReentrantLock(); // exclusive for writers; held by first reader
static int readCount = 0;
static void reader(String name, int delayBeforeMs) {
try {
Thread.sleep(delayBeforeMs);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
mutex.lock();
try {
readCount++;
if (readCount == 1) {
writeLock.lock();
}
} finally {
mutex.unlock();
}
// reading
System.out.println("[" + name + "] READ X = " + X);
mutex.lock();
try {
readCount--;
if (readCount == 0) {
writeLock.unlock();
}
} finally {
mutex.unlock();
}
}
static void writer(String op, String name, int delayBeforeMs, int delayBetweenMs) {
try {
Thread.sleep(delayBeforeMs);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
writeLock.lock();
try {
int oldVal = X;
System.out.println("[" + name + "] READ X = " + oldVal);
if (delayBetweenMs > 0) {
try { Thread.sleep(delayBetweenMs); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
int nw = oldVal;
if ("inc".equals(op)) nw = oldVal + 1;
else if ("dec".equals(op)) nw = oldVal - 1;
else {
try { nw = Integer.parseInt(op); } catch (NumberFormatException ex) { /* keep old */ }
}
X = nw;
System.out.println("[" + name + "] WROTE X = " + X);
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) throws Exception {
X = 5;
System.out.println("Initial X = " + X);
Thread w1 = new Thread(() -> writer("inc", "W1", 0, 500));
Thread w2 = new Thread(() -> writer("dec", "W2", 50, 0));
Thread r1 = new Thread(() -> reader("R1", 700));
Thread r2 = new Thread(() -> reader("R2", 1200));
w1.start();
w2.start();
r1.start();
r2.start();
w1.join();
w2.join();
r1.join();
r2.join();
System.out.println("Final X = " + X);
}
}
The output of the above Java code will look something like this −
Initial X = 5 [W1] READ X = 5 [W1] WROTE X = 6 [W2] READ X = 6 [W2] WROTE X = 5 [R1] READ X = 5 [R2] READ X = 5 Final X = 5
Conclusion
The Reader Writer Problem is a classic synchronization problem that shows the challenges of managing concurrent access to shared resources. We implemented a solution using semaphores to allow multiple readers to access the resource simultaneously while ensuring exclusive access for writers. This approach helps prevent data inconsistency and ensures the integrity of the shared resource.