Hello! Welcome to the first lesson in our module on deadlock.
In the previous modules, you've built a solid toolkit of synchronization primitives like mutexes, read-write locks, and semaphores. These tools are essential for protecting shared data. However, using them, especially when a thread needs to acquire more than one lock, introduces one of the most notorious problems in concurrent programming: deadlock.
This lesson provides the theoretical foundation you need to understand, and later prevent, this issue. Our goal is to describe the four necessary conditions for deadlock to occur. If you can understand these conditions, you can learn to write code that breaks at least one of them, making deadlock impossible.
What is a Deadlock?
At its core, a deadlock is a state where a group of threads is stuck, and no thread can proceed because each is waiting for a resource held by another thread in the same group. This creates a circular dependency that can freeze parts of your application, or the entire thing.
To build your intuition, let's start with a few analogies.
L-4.1: DEADLOCK concept | Example | Necessary condition | Operating System
The video "L-4.1: DEADLOCK concept" from Gate Smashers provides some excellent, easy-to-understand analogies for deadlock before moving into a technical example. This will help ground the concept.
Watch the video from the beginning until the 5:35 mark. First, focus on the real-world examples (the bank account and the cars) to get a feel for the general problem (0:00 - 2:10). Then, pay close attention to the technical example involving two processes (P1, P2) and two resources (R1, R2). This maps directly to the kind of scenarios we'll encounter with threads and mutexes (2:10 - 5:35).
The video's technical example is key:
- Process P1 holds Resource R1 and waits for R2.
- Process P2 holds Resource R2 and waits for R1.
Neither can proceed. This is often called a "deadly embrace."
The Four Necessary Conditions for Deadlock
For a deadlock to occur, four specific conditions must be met simultaneously. These are often called the Coffman conditions, named after their description in a 1971 paper by Edward G. Coffman, Jr. If you can ensure that at least one of these conditions is never met in your system, you can prevent deadlocks.
Conditions for Deadlock in Operating System
The article "Conditions for Deadlock in Operating System" from GeeksForGeeks provides a clear and concise explanation of these four conditions.
Please read the section titled "Necessary Conditions for the Occurrence of a Deadlock", which covers all four points. As you read, think about how each condition relates to the pthread_mutex_t you've already used.
Let's break down each condition and connect it to your C programming context.
1. Mutual Exclusion
- Definition: At least one resource must be non-shareable, meaning only one thread can use it at a time.
- In Practice: This condition is inherent to the tools we use for protecting critical sections. A
pthread_mutex_tis, by definition, a mutual exclusion lock. When you callpthread_mutex_lock, you are establishing this condition for the resource the mutex protects. For many resources (like a specific memory location or I/O device), mutual exclusion is unavoidable.
2. Hold and Wait
- Definition: A thread is holding at least one resource while simultaneously waiting to acquire additional resources held by other threads.
- In Practice: This happens anytime a thread successfully locks one mutex and then blocks while trying to lock a second one. For example:
If Thread 2 holds// Thread 1 pthread_mutex_lock(&mutex_A); // ... is now 'holding' mutex_A pthread_mutex_lock(&mutex_B); // Tries to lock mutex_B, may block. Now 'waiting'.mutex_Bat this moment, Thread 1 is in a state of "hold and wait."
3. No Preemption
- Definition: A resource cannot be forcibly taken away from the thread that holds it. The thread must release it voluntarily.
- In Practice: The Pthreads API enforces this. There is no
pthread_mutex_force_unlock()function that lets one thread break another thread's lock. A lock acquired by a thread withpthread_mutex_lockcan only be released by the same thread callingpthread_mutex_unlock. The operating system scheduler will not preempt a thread and take its locks away.
4. Circular Wait
- Definition: A set of threads {T1, T2, ..., Tn} exists where T1 is waiting for a resource held by T2, T2 is waiting for a resource held by T3, and so on, until Tn is waiting for a resource held by T1.
- In Practice: This is the condition that creates the "deadly embrace." It's the cycle of dependencies that brings everything to a halt. The simplest case involves two threads and two mutexes:
- Thread 1: Locks
mutex_A, then tries to lockmutex_B. - Thread 2: Locks
mutex_B, then tries to lockmutex_A.
- Thread 1: Locks
If the scheduler interleaves their execution just right, Thread 1 will hold A and wait for B, while Thread 2 holds B and waits for A. A perfect circle of waiting is formed.

Test your understanding!
Consider a system with two threads, T_alpha and T_beta, and two mutexes, M1 and M2.
The code for T_alpha is:
pthread_mutex_lock(&M1);
// do some work...
pthread_mutex_lock(&M2);
// do more work...
pthread_mutex_unlock(&M2);
pthread_mutex_unlock(&M1);
The code for T_beta is:
pthread_mutex_lock(&M2);
// do some work...
pthread_mutex_lock(&M1);
// do more work...
pthread_mutex_unlock(&M1);
pthread_mutex_unlock(&M2);
If T_alpha successfully locks M1 and is then preempted by the OS scheduler just before it can lock M2, and T_beta then runs and successfully locks M2, what happens next? Map this situation to the four conditions of deadlock.
Show answer
When T_beta tries to lock M1, it will block because T_alpha holds it. When T_alpha resumes, it will try to lock M2 and block because T_beta holds it. A deadlock occurs.
Here is how the four conditions are met:
- Mutual Exclusion:
M1andM2are mutexes, so they can only be held by one thread at a time. This condition is met. - Hold and Wait:
T_alphaholdsM1and is waiting forM2.T_betaholdsM2and is waiting forM1. This condition is met for both threads. - No Preemption:
T_alphacannot haveM1taken from it, andT_betacannot haveM2taken from it. They must be unlocked voluntarily. This condition is met. - Circular Wait:
T_alphais waiting for a resource (M2) held byT_beta, andT_betais waiting for a resource (M1) held byT_alpha. This forms a cycle of length two. This condition is met.
Since all four conditions are met, deadlock is inevitable.
The Dining Philosophers: A Classic Deadlock Problem
To solidify the concept of circular wait, let's look at a classic computer science problem that illustrates it perfectly: the Dining Philosophers.
Deadlock 3: Dining Philosophers
The video "Deadlock 3: Dining Philosophers" by Jacob Schrum provides an excellent visualization of this problem. It shows how a seemingly reasonable strategy for resource acquisition leads directly to a circular wait and deadlock.
Please watch two segments of this video: 0:00 - 1:53: Understand the problem setup. Note how the first three conditions (Mutual Exclusion, Hold and Wait, No Preemption) are naturally part of the problem's rules. 4:56 - 7:33: This part shows how a simple rule—"each philosopher picks up their right fork first"—inevitably leads to a circular wait and thus a deadlock. The resource allocation graph makes the cycle visually obvious.
The Dining Philosophers problem is a powerful mental model to keep in mind. It demonstrates how a system of independent actors, each following a simple and locally-sensible rule, can collectively create a global system failure.
Conclusion
In this lesson, we've defined deadlock and laid out the four conditions that must exist simultaneously for it to occur. Understanding these is the first, critical step toward writing deadlock-free concurrent code.
Key Takeaways:
- A deadlock is a state where a group of threads is permanently blocked, each waiting for a resource held by another in the group.
- For a deadlock to happen, all four Coffman conditions must be true:
- Mutual Exclusion: Resources are non-shareable (e.g., a mutex).
- Hold and Wait: A thread holds one resource while waiting for another.
- No Preemption: Resources cannot be forcibly taken away.
- Circular Wait: A closed chain of threads exists, each waiting for a resource held by the next thread in the chain.
Preview of the Next Lesson:
Knowing the causes is only half the battle. The real goal is prevention. Since all four conditions are necessary for deadlock, we can prevent it by breaking just one of them. In our next lesson, we will focus on the most common and practical strategy: breaking the circular wait condition by implementing a strict lock-ordering policy. This technique will allow you to safely acquire multiple mutexes without risking a deadly embrace.