- 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 - Dining Philosophers Problem
The Dining Philosophers Problem is a classic synchronization problem that shows the challenges of sharing resources between multiple processes or entities. It was formulated by Edsger Dijkstra in 1965. Read this chapter to understand the problem, it's conditions and its solution.
- Dining Philosophers Problem Explained
- Deadlock Situation
- Solution Using Semaphores
- Implementation in Python, C++ and Java
Dining Philosophers Problem Explained
Imagine a six philosophers sitting around a circular table. Each philosopher has two states, thinking and eating. To start eating, a philosopher needs both fork and spoon simultaneously. However, there are only three forks and three spoons available on the table, which are placed between the philosophers as shown in the figure below −
A philosopher can only use the fork and spoon that are adjacent to them. Our challenge is to design a protocol that allows the philosophers to eat without facing deadlock or starvation.
Deadlock Situation in Dining Philosophers Problem
Now, imagine all the philosophers starts eating at the same time and each picks up the cutlery on their left first. This will lead to a deadlock situation. Each philosopher will be holding one piece of cutlery (either fork or spoon) and waiting for the other piece to become available. Result is that none of them can eat.
P1 -> Picks Spoon on left -> Needs Fork on right.(held by P6) P2 -> Picks Fork on left -> Needs Spoon on right.(held by P1) P3 -> Picks Spoon on left -> Needs Fork on right.(held by P2) P4 -> Picks Fork on left -> Needs Spoon on right.(held by P3) P5 -> Picks Spoon on left -> Needs Fork on right.(held by P4) P6 -> Picks Fork on left -> Needs Spoon on right.(held by P5) Result: Deadlock - No one can eat.
Let's see how we can solve this problem using the concept of semaphores.
Dining Philosophers Solution Using Semaphores
Semaphores are synchronization primitives that can be used to control access to shared resources. In this case, we can use semaphores to manage the availability of forks and spoons.
Algorithm
- Both forks and spoons are represented as binary semaphores initialized to 1 (available).
- A philosopher must acquire both the fork and spoon before eating.
- After eating, the philosopher releases both the fork and spoon.
The pseudo code for the philosopher's behavior can be represented as follows −
semaphore fork[3] = {1, 1, 1}; // Three forks
semaphore spoon[3] = {1, 1, 1}; // Three spoons
void philosopher(int i) {
while (true) {
think(); // Philosopher is thinking
// Pick up fork and spoon
wait(fork[i]); // Pick up fork
wait(spoon[i]); // Pick up spoon
eat(); // Philosopher is eating
// Put down fork and spoon
signal(spoon[i]); // Put down spoon
signal(fork[i]); // Put down fork
}
}
In this algorithm, each philosopher first thinks and then attempts to pick up the fork and spoon on their left. After eating, they put down both utensils. This approach ensures that no two philosophers can hold the same utensil at the same time, preventing deadlock.
Still, there is a possibility of deadlock. If all philosophers pick up the left cutlery at the same time, they will all be waiting for the cutlery at right, leading to deadlock. To prevent this, we can introduce a new rule, only N-1 philosophers can start eating at the same time, where N is the total number of philosophers. This prevents the circular wait condition, thus avoiding deadlock.
Implementation
Here is a simple implementation of the Dining Philosophers Problem using semaphores in C++/ Python and Java:
Note: We assumed both forks and spoons as same entity (stored in forks array) and ensured a philosopher must get left and right utensils to start eating. This will simplify the implementation.
import threading
import time
import random
N = 6
forks = [threading.Lock() for _ in range(N)]
waiter = threading.Semaphore(N - 1)
stop_event = threading.Event() # <-- global stop flag
def philosopher(i):
left = forks[i]
right = forks[(i + 1) % N]
while not stop_event.is_set(): # <-- stop condition
print(f"Philosopher {i} is thinking.")
time.sleep(random.uniform(0.2, 0.6)) # shorter for clarity
if stop_event.is_set():
break
if not waiter.acquire(timeout=0.5): # avoid blocking forever
continue
if not left.acquire(timeout=0.5):
waiter.release()
continue
if not right.acquire(timeout=0.5):
left.release()
waiter.release()
continue
try:
print(f"Philosopher {i} is eating.")
time.sleep(random.uniform(0.2, 0.6))
finally:
right.release()
left.release()
waiter.release()
# Start threads
threads = []
for i in range(N):
t = threading.Thread(target=philosopher, args=(i,))
threads.append(t)
t.start()
# Let the simulation run for 5 seconds
time.sleep(5)
stop_event.set() # signal all philosophers to stop
# Wait for threads to finish
for t in threads:
t.join()
print("Simulation finished.")
The output of the above code will be similar to below −
Philosopher 0 is thinking. Philosopher 1 is thinking. Philosopher 2 is thinking. Philosopher 3 is thinking. Philosopher 4 is thinking. Philosopher 5 is thinking. Philosopher 2 is eating. Philosopher 5 is eating. Philosopher 2 is thinking. ....... Stimulation finished.
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <random>
using namespace std::chrono_literals;
class Semaphore {
public:
explicit Semaphore(int initial) : count(initial) {}
// try to acquire within timeout; returns true if acquired
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;
}
void release() {
std::lock_guard<std::mutex> lk(m);
++count;
cv.notify_one();
}
private:
std::mutex m;
std::condition_variable cv;
int count;
};
int main() {
const int N = 6;
std::vector<std::timed_mutex> forks(N);
Semaphore waiter(N - 1);
std::atomic<bool> stop_flag{false};
auto philosopher = [&](int i) {
// per-thread RNG
std::mt19937 rng(std::random_device{}() ^ (i << 8));
std::uniform_int_distribution<int> think_ms(200, 600);
std::uniform_int_distribution<int> eat_ms(200, 600);
while (!stop_flag.load(std::memory_order_relaxed)) {
std::cout << "Philosopher " << i << " is thinking." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(think_ms(rng)));
// request permission from the waiter (timeout so we can exit)
if (!waiter.try_acquire_for(500ms)) continue;
bool left_locked = false, right_locked = false;
// try acquire left fork
left_locked = forks[i].try_lock_for(500ms);
if (!left_locked) {
waiter.release();
continue;
}
// try acquire right fork
right_locked = forks[(i + 1) % N].try_lock_for(500ms);
if (!right_locked) {
forks[i].unlock();
waiter.release();
continue;
}
// Both forks acquired
try {
std::cout << "Philosopher " << i << " is eating." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(eat_ms(rng)));
} catch (...) {
// not expected, but ensure release
}
// release forks and waiter
forks[(i + 1) % N].unlock();
forks[i].unlock();
waiter.release();
}
// exiting
//std::cout << "Philosopher " << i << " exiting.\n";
};
std::vector<std::thread> threads;
threads.reserve(N);
for (int i = 0; i < N; ++i) threads.emplace_back(philosopher, i);
// run for 5 seconds
std::this_thread::sleep_for(5s);
stop_flag.store(true);
for (auto &t : threads) if (t.joinable()) t.join();
std::cout << "Simulation finished.\n";
return 0;
}
The output of the above code will be similar to below −
Philosopher 0 is thinking. Philosopher 1 is thinking. Philosopher 2 is thinking. Philosopher 3 is thinking. Philosopher 4 is thinking. Philosopher 5 is thinking. Philosopher 3 is eating. Philosopher 1 is eating. Philosopher 3 is thinking. ....... Simulation finished.
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Random;
public class Dining {
static final int N = 6;
public static void main(String[] args) throws InterruptedException {
final ReentrantLock[] forks = new ReentrantLock[N];
for (int i = 0; i < N; ++i) forks[i] = new ReentrantLock();
final Semaphore waiter = new Semaphore(N - 1);
final AtomicBoolean stop = new AtomicBoolean(false);
Thread[] threads = new Thread[N];
for (int i = 0; i < N; ++i) {
final int id = i;
threads[i] = new Thread(() -> {
Random rng = new Random(System.nanoTime() ^ (id << 8));
while (!stop.get()) {
System.out.println("Philosopher " + id + " is thinking.");
try {
Thread.sleep(200 + rng.nextInt(401)); // 200â600 ms
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
boolean gotWaiter = false;
boolean leftLocked = false;
boolean rightLocked = false;
try {
gotWaiter = waiter.tryAcquire(500, TimeUnit.MILLISECONDS);
if (!gotWaiter) continue;
leftLocked = forks[id].tryLock(500, TimeUnit.MILLISECONDS);
if (!leftLocked) continue;
rightLocked = forks[(id + 1) % N].tryLock(500, TimeUnit.MILLISECONDS);
if (!rightLocked) continue;
System.out.println("Philosopher " + id + " is eating.");
Thread.sleep(200 + rng.nextInt(401));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} finally {
if (rightLocked) forks[(id + 1) % N].unlock();
if (leftLocked) forks[id].unlock();
if (gotWaiter) waiter.release();
}
}
});
threads[i].start();
}
Thread.sleep(5000);
stop.set(true);
for (Thread t : threads) t.join();
System.out.println("Simulation finished.");
}
}
The output of the above code will be similar to below −
Philosopher 0 is thinking. Philosopher 1 is thinking. Philosopher 2 is thinking. Philosopher 3 is thinking. Philosopher 4 is thinking. Philosopher 5 is thinking. Philosopher 4 is eating. Philosopher 2 is eating. Philosopher 4 is thinking. ....... Simulation finished.
Conclusion
The Dining Philosophers Problem is a classic example of how to manage resource sharing and synchronization in concurrent programming. The idea behind solving this problem using semaphores is to ensure that no two philosophers can hold the same utensil at the same time. We also introduced a waiter to limit the number of philosophers who can attempt to eat simultaneously. This prevents circular wait and thus avoids deadlock.