OS Synchronization Simulator

Interactive Learning Tool for Operating Systems

About Synchronization Problems
Understanding process coordination in concurrent systems

This simulator demonstrates two classic synchronization problems in operating systems. These problems illustrate fundamental challenges in concurrent programming: managing shared resources, preventing race conditions, avoiding deadlocks, and ensuring fair access to resources.

The Sleeping Barber Problem

Models coordination between a barber and customers in a barbershop with limited waiting chairs. Demonstrates mutex and semaphore usage for process synchronization.

The Cigarette Smokers Problem

Illustrates coordination between multiple processes waiting for specific resource combinations. Shows how to handle conditional synchronization.

The Sleeping Barber Problem
Watch how synchronization solves coordination between barber and customers
0
Served
0
Waiting
0
Rejected

Barber Station

😴
Sleeping

Waiting Area (0/3 chairs)

📋 Event Log

Waiting for events...

Comprehensive Documentation

📖 What is the Sleeping Barber Problem?

The Sleeping Barber Problem is a classic inter-process communication and synchronization problem in computer science. It involves coordinating access to a shared resource (the barber) between multiple processes (customers) in a way that prevents race conditions, deadlocks, and ensures fair resource allocation.

Core Components:

  • Barber: A shared resource that can serve one customer at a time
  • Waiting Room: Limited number of chairs for customers to wait
  • Customers: Multiple processes arriving at random times
  • Mutual Exclusion: Only one customer can be served at a time

⚠️ Why is it Considered a Problem?

Without proper synchronization mechanisms, several critical issues can occur:

🔴 Race Conditions

Multiple customers might try to wake the barber simultaneously, or access waiting chairs concurrently, leading to unpredictable behavior and data corruption.

🟡 Deadlock

The barber might wait for customers while customers wait for the barber, creating a circular dependency where no progress can be made.

🟣 Starvation

Some customers might wait indefinitely if new customers keep arriving and the queue management isn't fair (though our implementation uses FIFO to prevent this).

🟠 Resource Waste

The barber sleeping when customers are waiting, or customers leaving because they can't determine if chairs are available, wastes CPU cycles and resources.

🌍 Real-World Applications

🏥 Hospital Emergency Room Management

Scenario: An emergency room has limited doctors (barbers) and a waiting area with limited seats (chairs).

  • Patients arrive randomly at different times with varying urgency levels
  • Doctors rest when no patients are waiting (sleeping barber)
  • Patients must wait if doctors are busy and seats are available
  • Patients leave if the waiting room is full (rejected customers)
  • Synchronization needed: Proper queue management, doctor notification, patient tracking
💻 Web Server Request Processing

Scenario: A web server has worker threads (barbers) and a request queue with limited size (chairs).

  • HTTP requests arrive from clients at unpredictable rates
  • Worker threads sleep when idle to conserve resources
  • Requests queue up when all workers are busy
  • New requests rejected with 503 Service Unavailable if queue is full
  • Synchronization needed: Thread pool management, request queuing, load balancing
🖨️ Print Spooler System

Scenario: A network printer (barber) serves multiple computers (customers) with a print queue (waiting room).

  • Print jobs arrive from various users throughout the day
  • Printer enters sleep mode when no jobs are queued (energy saving)
  • Jobs wait in queue when printer is busy with current job
  • Jobs rejected if spooler buffer is full
  • Synchronization needed: Queue management, printer wake-up, job status tracking

Synchronization Mechanisms

The Sleeping Barber Problem can be solved using various synchronization primitives. Here are the most common approaches:

1️⃣ Semaphore-Based Solution

Uses three semaphores to coordinate between barber and customers:

// Semaphore declarations Semaphore customers = 0; // Number of customers waiting Semaphore barber = 0; // Barber ready to cut hair Semaphore mutex = 1; // Protect waiting count access int waiting = 0; // Number of customers waiting int chairs = N; // Number of waiting chairs // Customer process customerArrival() { mutex.wait(); // Lock if (waiting < chairs) { waiting++; // Sit in waiting room mutex.signal(); // Unlock customers.signal(); // Wake barber if sleeping barber.wait(); // Wait for barber getHaircut(); } else { mutex.signal(); // Unlock leaveShop(); // No chairs, leave } } // Barber process barberProcess() { while (true) { customers.wait(); // Sleep until customer arrives mutex.wait(); // Lock waiting--; // Customer leaving waiting room mutex.signal(); // Unlock barber.signal(); // Ready to cut hair cutHair(); // Serve customer } }
How it solves the problem:
  • mutex ensures atomic access to shared waiting count
  • customers signals barber when customer arrives
  • barber signals customer when ready to serve
  • Prevents race conditions through mutual exclusion
  • Avoids deadlock with proper signal ordering
2️⃣ Monitor-Based Solution

Uses a monitor with condition variables for cleaner synchronization:

monitor BarberShop { int waiting = 0; int chairs = N; condition customerReady, barberReady; procedure customerArrival() { if (waiting < chairs) { waiting++; signal(customerReady); // Wake barber wait(barberReady); // Wait for service waiting--; } else { // Shop full, leave return; } } procedure barberService() { if (waiting == 0) { wait(customerReady); // Sleep until customer } signal(barberReady); // Ready to serve cutHair(); } }
Advantages: Higher-level abstraction, automatic mutual exclusion within monitor, easier to understand and maintain, less error-prone than semaphores.
3️⃣ This Simulator's Approach (Event-Driven State Machine)

Our implementation uses React state management with careful synchronization:

// State management const [customers, setCustomers] = useState<Customer[]>([]); const [barberStatus, setBarberStatus] = useState('sleeping'); const processingRef = useRef(false); // Prevent concurrent processing // Customer arrival (separate interval) useEffect(() => { const interval = setInterval(() => { if (Math.random() * 100 < arrivalRate) { setCustomers(prev => { if (prev.filter(c => c.status === 'waiting').length >= chairs) { logEvent('Customer rejected - shop full'); return prev; // Shop full } logEvent('Customer arrived'); return [...prev, { id: nextId++, status: 'waiting' }]; }); } }, speed * 1.5); return () => clearInterval(interval); }, [arrivalRate, speed]); // Barber processing (separate interval) useEffect(() => { const interval = setInterval(() => { if (processingRef.current) return; // Guard against concurrent processingRef.current = true; setCustomers(prev => { const waiting = prev.filter(c => c.status === 'waiting'); const serving = prev.filter(c => c.status === 'getting-haircut'); if (serving.length > 0) { // Finish current customer logEvent('Customer finished'); setBarberStatus(waiting.length > 0 ? 'idle' : 'sleeping'); return prev.filter(c => c.id !== serving[0].id); } if (waiting.length > 0 && barberStatus !== 'cutting') { // Start serving next customer logEvent('Barber woke up, starting haircut'); setBarberStatus('cutting'); return prev.map(c => c.id === waiting[0].id ? { ...c, status: 'getting-haircut' } : c ); } return prev; }); processingRef.current = false; }, speed); return () => clearInterval(interval); }, [speed, barberStatus]);
Key Features:
  • processingRef prevents race conditions (acts like mutex)
  • Separate intervals for arrival and processing avoid conflicts
  • Immutable state updates ensure predictable behavior
  • FIFO queue (array) ensures fairness and prevents starvation
  • Event logging provides visibility into synchronization

Implementation Best Practices

✅ Do's
  • Use proper locking mechanisms (mutex, semaphores)
  • Implement atomic operations for shared variables
  • Design with clear state transitions
  • Add timeout mechanisms to prevent indefinite waits
  • Log events for debugging and monitoring
  • Use FIFO queues for fairness
  • Clean up resources properly (clear intervals)
❌ Don'ts
  • Don't access shared state without synchronization
  • Avoid busy-waiting (polling in tight loops)
  • Don't create circular dependencies in locking
  • Never assume execution order without guarantees
  • Don't ignore race condition possibilities
  • Avoid global mutable state when possible
  • Don't forget to release locks/semaphores

💡 Key Takeaways

  • The Sleeping Barber Problem teaches fundamental concepts in concurrent programming and resource management
  • Proper synchronization is critical - without it, race conditions and deadlocks will occur
  • Multiple solution approaches exist (semaphores, monitors, message passing) - choose based on your platform
  • Real-world applications are everywhere: servers, databases, operating systems, embedded systems
  • Modern solutions often use higher-level abstractions (async/await, channels) built on these primitives
  • Testing concurrent systems is challenging - simulators like this help visualize and understand behavior

💡 Quick Reference - How it works:

  • The barber sleeps when no customers are waiting
  • Customers wake the barber when they arrive
  • If all chairs are full, new customers leave
  • Semaphores ensure proper synchronization and prevent race conditions
Key Synchronization Concepts

🔒 Mutual Exclusion

Ensures only one process accesses a shared resource at a time, preventing race conditions.

🚦 Semaphores

Synchronization primitives used to control access to shared resources and coordinate processes.

⚡ Race Conditions

Situations where the outcome depends on the timing of uncontrolled events, leading to unpredictable results.