Course contentsShow
← Rust
Lesson 1087 of 1,91010. Concurrency PrimitivesFree lesson

Deadlock Avoidance Strategies

You will learn to design locking hierarchies and protocols to prevent threads from waiting indefinitely on each other.

In this lesson, you will learn how to design locking hierarchies and protocols to prevent threads from waiting indefinitely on each other.

A deadlock occurs when two or more threads are stuck in a "circular wait" condition: Thread A holds Lock 1 and waits for Lock 2, while Thread B holds Lock 2 and waits for Lock 1. Neither can proceed, and the application hangs. To prevent this, we use a strategy called Lock Ordering.

Establishing a Lock Hierarchy

The simplest way to avoid circular waits is to define a strict global order for acquiring locks. If your application needs to acquire both Mutex_A and Mutex_B, every thread must be programmed to lock them in the exact same sequence—for example, always A then B. If a thread needs B but doesn't have A, it must be designed to either release B or ensure it follows the hierarchy.

Consider this scenario where two threads attempt to update two different Mutex values:

use std::sync::Mutex;

let lock_a = Mutex::new(0);
let lock_b = Mutex::new(0);

// Thread 1 and Thread 2 both agree: always lock A, then B.
let _a = lock_a.lock().unwrap();
let _b = lock_b.lock().unwrap();

By enforcing that all threads acquire lock_a before lock_b, it becomes impossible for a circular dependency to form. Thread 2 will simply block at lock_a before it ever reaches the point where it could hold lock_b and block Thread 1.

Fine-Grained Locking Protocols

When a global hierarchy is too restrictive, we use Try-Lock protocols. Instead of waiting indefinitely for a lock, a thread attempts to acquire it and, if it fails, it rolls back its state:

if let Ok(guard_a) = lock_a.try_lock() {
    if let Ok(guard_b) = lock_b.try_lock() {
        // Perform work
    } else {
        // Release guard_a and retry later
    }
}

By using try_lock(), you convert a "wait indefinitely" scenario into a "retry later" scenario, which effectively breaks the hold-and-wait condition required for a deadlock to persist. Always favor clear, consistent locking orders in your architecture to keep your concurrent systems predictable and hang-free.

Key Takeaway: Prevent deadlocks by establishing a strict global hierarchy for lock acquisition or by using non-blocking retry protocols when a resource is unavailable.