Deadlocks and Prevention

[!NOTE] This module explores the core principles of Deadlocks and Prevention, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.

1. The Deadly Embrace

A Deadlock is a state where a set of processes are blocked because each process is holding a resource and waiting for another resource acquired by some other process.

Imagine two trains approaching each other on a single track. Neither can move forward until the other moves back. Neither moves back. They are stuck forever.

The 4 Necessary Conditions (Coffman Conditions)

A deadlock can arise if and only if all four of these conditions hold simultaneously:

  1. Mutual Exclusion: At least one resource must be non-shareable. (e.g., A printer or a mutex).
  2. Hold and Wait: A process holding at least one resource is waiting to acquire additional resources held by other processes.
  3. No Preemption: Resources cannot be forcibly taken from a process. They must be released voluntarily.
  4. Circular Wait: A set of processes {P0, P1, ..., Pn} exists such that P0 waits for P1, P1 waits for P2, …, and Pn waits for P0.

2. Interactive: The Cycle of Doom

Build a deadlock step-by-step.

  1. Thread A acquires Lock 1.
  2. Thread B acquires Lock 2.
  3. Thread A tries to acquire Lock 2 (and waits).
  4. Thread B tries to acquire Lock 1 (and waits). Result: DEADLOCK!
T1
T2
L1
L2
System Running...


3. Prevention: Resource Ordering

The most practical way to prevent deadlocks is to break the Circular Wait condition. Rule: Assign a global order to all resources (e.g., L1 < L2). All threads must acquire resources in increasing order.

If T1 wants L1 and L2, it must grab L1 first. If T2 wants L1 and L2, it must also grab L1 first. Now, T2 will block on L1 while T1 holds it. T2 cannot grab L2 yet. No cycle is possible.


4. Code Example: Causing and Fixing Deadlock

// Broken: Deadlock prone
void methodA() {
    synchronized(lock1) {
        synchronized(lock2) { /* ... */ }
    }
}

void methodB() {
    synchronized(lock2) { // WRONG ORDER
        synchronized(lock1) { /* ... */ }
    }
}

// Fixed: Consistent Ordering
void methodB_Fixed() {
    synchronized(lock1) { // ALWAYS Lock 1 First
        synchronized(lock2) { /* ... */ }
    }
}
package main

import "sync"

func main() {
    var mu sync.Mutex
    mu.Lock()
    mu.Lock() // DEADLOCK!
    // Go Runtime will panic:
    // "fatal error: all goroutines are asleep - deadlock!"
}