Operating System - Sleeping Barber Problem



The Sleeping Barber Problem is a classic synchronization problem in operating systems to show the challenges of process synchronization and inter-process communication. Read this chapter to understand the problem statement, its solution using semaphores, and the implementation details.

Sleeping Barber Problem Explained

In the Sleeping Barber Problem, we have a barber shop with one barber, one barber chair, and a waiting room with a N number of chairs for waiting customers.

  • When a customer comes to the barber shop, they check if there are any free chairs in the waiting room.
  • If there is a free chair, the customer sits in the waiting room. If all chairs are occupied, the customer leaves the shop.
  • If the barber is busy cutting hair, the customer waits in the waiting room.
  • If there are no customers, the barber goes to sleep in the barber chair.
  • When a customer arrives and finds the barber sleeping, they wake up the barber to get their hair cut.

The diagram below illustrates the Sleeping Barber Problem −

Sleeping Barber Problem Diagram

In this problem, our task is to ensure that the barber and customers are synchronized properly such that −

  • The barber sleeps when there are no customers.
  • Customers wait if the barber is busy.
  • No customer leaves the shop if there is a free chair in the waiting room.

Solution of Sleeping Barber Problem

To solve the Sleeping Barber Problem, we can use semaphores to manage access to the barber and waiting room. We can use the following semaphores:

  • waitingRoom − This semaphore keeps the count of available chairs in the waiting room.
  • barberChair − A binary semaphore to indicate whether the barber chair is occupied or not. It is initialized to 1 (available).
  • barberSleep − This also a binary semaphore used to put the barber to sleep when there are no customers. It is initialized to 0 (sleeping).

Now, let's define the functions indicating the behavior of the barber and customers −

// Barber process
void barber() {
    while (true) {
        wait(barberSleep); // Sleep if there are no customers
        wait(barberChair); // Acquire the barber chair
        // Cut hair
        signal(barberChair); // Release the barber chair
    }
}

To indicate the behavior of the customer, we can define the following function −

// Customer process
void customer() {
    if (wait(waitingRoom) == 0) { // Check for available chairs
        signal(waitingRoom); // Leave if no chairs are available
        return;
    }
    wait(barberChair); // Acquire the barber chair
    signal(barberSleep); // Wake up the barber if sleeping
    // Get hair cut
    signal(barberChair); // Release the barber chair
}

In this solution, the barber waits on the barberSleep semaphore when there are no customers. When a customer arrives, they check the waitingRoom semaphore to see if there are available chairs. If a chair is available, the customer sits and wakes up the barber if necessary. The barber then cuts the customer's hair and releases the barber chair for the next customer.

Implementation of Sleeping Barber Problem

The section below implements the solution for the Sleeping Barber Problem in Python, C++, and Java.

Note: The solution uses multithreading to simulate the barber and customers as separate threads.

import threading
import time
import random

# Semaphores  
waitingRoom = threading.Semaphore(3) 
barberChair = threading.Semaphore(1) 
barberSleep = threading.Semaphore(0) 
stop_event = threading.Event()       
print_lock = threading.Lock()        # Prevents text overlap

# Setting print lock
def safe_print(message):
    with print_lock:
        print(message)

def barber():
    safe_print("Barber: I am sleeping...")
    while not stop_event.is_set():
        # Wait for a customer
        customer_arrived = barberSleep.acquire(timeout=0.5)
        
        if stop_event.is_set():
            break

        if customer_arrived:
            safe_print("Barber: Woke up. Cutting hair...")
            time.sleep(random.uniform(0.5, 1.5)) # Cutting hair
            barberChair.release()                # Done
            safe_print("Barber: Finished cutting hair.")
    
    safe_print("Barber: Shop is closed. Going home.")

def customer(id):
    # 1. Check if shop is closed before entering
    if stop_event.is_set():
        return

    safe_print(f"Customer {id}: Arrived.")

    # 2. Try to enter waiting room
    if not waitingRoom.acquire(blocking=False):
        safe_print(f"Customer {id}: Waiting room full, leaving.")
        return
    
    safe_print(f"Customer {id}: Entered waiting room.")

    # 3. Wait for the barber chair
    barberChair.acquire()
    
    # Check if shop closed while we were waiting
    if stop_event.is_set():
        safe_print(f"Customer {id}: Sat in chair, but shop is closed! Leaving.")
        barberChair.release() # Release chair immediately
        waitingRoom.release() # Release waiting room spot
        return 

    waitingRoom.release()  # Leave waiting room
    barberSleep.release()  # Wake up the barber
    
    safe_print(f"Customer {id}: Getting a haircut.")
    
    # Wait for barber to finish    
    time.sleep(0.1) # Small buffer to let barber start
    
    # Wait for barber to release the chair  
    safe_print(f"Customer {id}: Haircut done. Leaving.")

# --- Execution ---

# Start Barber
barber_thread = threading.Thread(target=barber)
barber_thread.start()

# Start Generator in background
def customer_generator():
    for i in range(10):
        if stop_event.is_set(): break
        time.sleep(random.uniform(0.1, 0.5))
        threading.Thread(target=customer, args=(i,)).start()

gen_thread = threading.Thread(target=customer_generator)
gen_thread.start()

# Run Simulation for 5 Seconds
time.sleep(5)

# Stop Everything
safe_print("\n--- 5 SECONDS UP: CLOSING SHOP ---\n")
stop_event.set()

# Cleanup
barber_thread.join()
safe_print("Simulation ended.")

The output of the above code will be similar to −

Barber: I am sleeping...
Customer 0: Arrived.
Customer 0: Entered waiting room.
Customer 0: Getting a haircut.
Barber: Woke up. Cutting hair...
Customer 0: Haircut done. Leaving.
Customer 1: Arrived.
Customer 1: Entered waiting room.
...
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <chrono>
#include <atomic>
#include <random>

using namespace std::chrono_literals;

class Semaphore {
public:
    explicit Semaphore(int initial = 0) : count(initial) {}
    void release() {
        std::lock_guard<std::mutex> lk(m);
        ++count;
        cv.notify_one();
    }
    // blocking acquire
    void acquire() {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, [&]{ return count > 0; });
        --count;
    }
    // try acquire with timeout
    bool try_acquire_for(std::chrono::milliseconds timeout) {
        std::unique_lock<std::mutex> lk(m);
        if (!cv.wait_for(lk, timeout, [&]{ return count > 0; })) return false;
        --count;
        return true;
    }
    // try non-blocking
    bool try_acquire() {
        std::lock_guard<std::mutex> lk(m);
        if (count > 0) { --count; return true; }
        return false;
    }
    int available() {
        std::lock_guard<std::mutex> lk(m);
        return count;
    }
private:
    std::mutex m;
    std::condition_variable cv;
    int count;
};

static Semaphore waitingRoom(3);
static Semaphore barberChair(1);
static Semaphore barberSleep(0);
static std::atomic<bool> stop_flag(false);
static std::mutex print_m;

void safe_print(const std::string &s) {
    std::lock_guard<std::mutex> lk(print_m);
    std::cout << s << std::endl;
}

void barber_thread_fn() {
    std::mt19937 rng(std::random_device{}());
    std::uniform_int_distribution<int> dist(500, 1500);
    safe_print("Barber: I am sleeping...");
    while (!stop_flag.load()) {
        bool customerArrived = barberSleep.try_acquire_for(500ms);
        if (stop_flag.load()) break;
        if (customerArrived) {
            safe_print("Barber: Woke up. Cutting hair...");
            std::this_thread::sleep_for(std::chrono::milliseconds(dist(rng)));
            barberChair.release();
            safe_print("Barber: Finished cutting hair.");
        }
    }
    safe_print("Barber: Shop is closed. Going home.");
}

void customer_fn(int id) {
    if (stop_flag.load()) return;
    safe_print("Customer " + std::to_string(id) + ": Arrived.");

    if (!waitingRoom.try_acquire()) {
        safe_print("Customer " + std::to_string(id) + ": Waiting room full, leaving.");
        return;
    }
    safe_print("Customer " + std::to_string(id) + ": Entered waiting room.");

    // Wait for barber chair, polling so we can react to stop
    while (!stop_flag.load()) {
        if (barberChair.try_acquire_for(200ms)) {
            // got the chair
            waitingRoom.release(); // left waiting room
            barberSleep.release(); // wake barber
            safe_print("Customer " + std::to_string(id) + ": Getting a haircut.");
            std::this_thread::sleep_for(100ms); // small buffer
            safe_print("Customer " + std::to_string(id) + ": Haircut done. Leaving.");
            return;
        }
    }
    // shop closed while waiting
    safe_print("Customer " + std::to_string(id) + ": Shop closed while waiting, leaving.");
    // release waitingRoom slot if we previously held it; but in this pattern we've already acquired it earlier,
    // and either released it when getting the chair or we still hold it. For simplicity, release safely:
    if (waitingRoom.available() < 3) waitingRoom.release();
}

int main() {
    std::vector<std::thread> customers;
    std::thread barber(barber_thread_fn);

    // generator thread
    std::thread gen([&](){
        std::mt19937 rng(std::random_device{}());
        std::uniform_int_distribution<int> genDelay(100, 500);
        for (int i = 0; i < 10 && !stop_flag.load(); ++i) {
            std::this_thread::sleep_for(std::chrono::milliseconds(genDelay(rng)));
            customers.emplace_back(customer_fn, i);
        }
    });

    // run 5 seconds
    std::this_thread::sleep_for(5s);
    safe_print("\n--- 5 SECONDS UP: CLOSING SHOP ---\n");
    stop_flag.store(true);

    // wake barber (in case sleeping) so it can exit promptly
    barberSleep.release();

    gen.join();
    barber.join();

    for (auto &t : customers) if (t.joinable()) t.join();

    safe_print("Simulation ended.");
    return 0;
}

The output of the above code will be similar to −

Barber: I am sleeping...
Customer 0: Arrived.
Customer 0: Entered waiting room.
Customer 0: Getting a haircut.
Barber: Woke up. Cutting hair...
Customer 0: Haircut done. Leaving.
...
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class BarberShop {
    static final int WAITING_CAPACITY = 3;
    static final Semaphore waitingRoom = new Semaphore(WAITING_CAPACITY);
    static final Semaphore barberChair = new Semaphore(1);
    static final Semaphore barberSleep = new Semaphore(0);
    static final AtomicBoolean stop = new AtomicBoolean(false);
    static final ReentrantLock printLock = new ReentrantLock();

    static void safePrint(String msg) {
        printLock.lock();
        try { System.out.println(msg); }
        finally { printLock.unlock(); }
    }

    public static void main(String[] args) throws InterruptedException {
        Random rng = new Random();

        Thread barber = new Thread(() -> {
            safePrint("Barber: I am sleeping...");
            while (!stop.get()) {
                try {
                    // Wait for a customer or timeout to re-check stop flag
                    boolean customerArrived = barberSleep.tryAcquire(500, TimeUnit.MILLISECONDS);
                    if (stop.get()) break;
                    if (customerArrived) {
                        safePrint("Barber: Woke up. Cutting hair...");
                        Thread.sleep(500 + rng.nextInt(1001)); // 500-1500ms
                        barberChair.release(); // done with chair
                        safePrint("Barber: Finished cutting hair.");
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
            safePrint("Barber: Shop is closed. Going home.");
        }, "Barber");

        List<Thread> customerThreads = new ArrayList<>();

        Runnable customerTaskFactory = () -> {
            int id;
            synchronized (customerThreads) { id = customerThreads.size(); } // assign id
            Thread t = new Thread(() -> {
                if (stop.get()) return;
                safePrint("Customer " + id + ": Arrived.");

                // Try to enter waiting room (non-blocking)
                if (!waitingRoom.tryAcquire()) {
                    safePrint("Customer " + id + ": Waiting room full, leaving.");
                    return;
                }
                safePrint("Customer " + id + ": Entered waiting room.");

                // Wait for barber chair (polling with timeout so we can react to stop)
                try {
                    while (!stop.get()) {
                        if (barberChair.tryAcquire(200, TimeUnit.MILLISECONDS)) {
                            // We got the chair
                            waitingRoom.release();   // we left waiting room to sit in chair
                            barberSleep.release();   // wake up barber
                            safePrint("Customer " + id + ": Getting a haircut.");
                            // The barber will release the chair after finishing the cut.
                            // We just wait a short time to simulate staying in chair
                            Thread.sleep(100); // small buffer
                            safePrint("Customer " + id + ": Haircut done. Leaving.");
                            return;
                        }
                    }
                    // If shop closed while waiting:
                    if (stop.get()) {
                        safePrint("Customer " + id + ": Shop closed while waiting, leaving.");
                        // If we still occupy waiting room slot, release it
                        if (waitingRoom.availablePermits() < WAITING_CAPACITY) {
                            // Try to release — but be cautious not to over-release:
                            // We only release here if we previously acquired and didn't release.
                            // The safe approach is to attempt to release only if we know we held it.
                            // In this simple example we just ensure the slot is released above when necessary.
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "Customer-" + id);

            synchronized (customerThreads) {
                customerThreads.add(t);
            }
            t.start();
        };

        // Start barber
        barber.start();

        // Start generator thread that spawns some customers
        Thread generator = new Thread(() -> {
            for (int i = 0; i < 10 && !stop.get(); ++i) {
                try {
                    Thread.sleep(100 + rng.nextInt(401)); // 100-500ms
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
                customerTaskFactory.run();
            }
        }, "Generator");
        generator.start();

        // Let simulation run for 5 seconds
        Thread.sleep(5000);
        safePrint("\n--- 5 SECONDS UP: CLOSING SHOP ---\n");
        stop.set(true);

        // Wake barber in case it's sleeping so it can exit promptly
        barberSleep.release();

        // Wait for generator & barber to finish
        generator.join();
        barber.join();

        // Join customer threads
        synchronized (customerThreads) {
            for (Thread t : customerThreads) {
                try { t.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }

        safePrint("Simulation ended.");
    }
}

The output of the above code will be similar to −

Barber: I am sleeping...
Customer 0: Arrived.
Customer 0: Entered waiting room.
Barber: Woke up. Cutting hair...
Customer 0: Getting a haircut.
Customer 0: Haircut done. Leaving.
Customer 1: Arrived.
...

Conclusion

The Sleeping Barber Problem shows how semaphores can be used to synchronize processes in a concurrent environment. To implement the solution, we just used 3 semaphores to manage access to the barber chair, waiting room, and barber sleep state. We used the concept of multithreading to simulate the barber and customers as separate threads, so that they concurrently as a separate entities similar to real-world scenario.

Advertisements