In technical terms, a deadlock is a situation in concurrent computing where two or more processes are blocked forever because they are waiting for each other to release a resource.
Imagine Process A holds Key 1 and needs Key 2, while Process B holds Key 2 and needs Key 1. Neither can proceed.
The best way to explain this to anyone (even non-techies) is the Traffic Intersection.
Imagine a four-way intersection with four cars.
Result: No car can move. The traffic is deadlocked forever unless an external force (police/tow truck) intervenes.
For a deadlock to occur, ALL FOUR of these conditions must be true simultaneously. If you break even one, the deadlock is resolved.
Resources cannot be shared. Only one process can use a resource at a time.
A process holding at least one resource is waiting to acquire additional resources held by others.
A resource cannot be taken from a process forcibly. It must be released voluntarily.
A set of processes are waiting for each other in a circular chain.
How do Operating Systems deal with this mess?
If we can guarantee that at least one of the conditions cannot hold, deadlock is impossible.
Challenge: Some resources like printers cannot be shared.
Challenge: Low resource utilization; starvation possible.
Commonly used in practical systems.
The OS requires additional information about which resources a process will need. Before granting a request, the OS checks: "Will granting this put me in an Unsafe State?"
A state is safe if the system can allocate resources to each process in some order and still avoid a deadlock.
If system remains in SAFE state.
If system would enter UNSAFE state.
The OS periodically runs an algorithm (like looking for cycles in a Resource Allocation Graph) to see if a deadlock exists.
Here we use std::mutex to demonstrate a classic deadlock.
Locks Resource A, sleeps (to let Thread 2 run), then tries to lock Resource B.
Locks Resource B, sleeps (to let Thread 1 run), then tries to lock Resource A.
Both threads end up waiting forever for the resource the other holds.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutexA;
std::mutex mutexB;
void thread1_func() {
mutexA.lock();
// Acquired A, waiting for B
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mutexB.lock(); // BLOCKS HERE FOREVER
mutexB.unlock();
mutexA.unlock();
}
void thread2_func() {
mutexB.lock();
// Acquired B, waiting for A
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mutexA.lock(); // BLOCKS HERE FOREVER
mutexA.unlock();
mutexB.unlock();
}
int main() {
std::thread t1(thread1_func);
std::thread t2(thread2_func);
t1.join();
t2.join();
return 0;
}
The Answer Strategy: Start with the definition, mention the 4 conditions, and give a quick example.
The Answer Strategy: Focus on breaking one of the 4 conditions.