MST

星途 面试题库

面试题:Rust并发编程中死锁常见场景及防范

在Rust并发编程中,请举例说明至少两种可能导致死锁的常见场景,并阐述如何利用Rust的特性来防范这些死锁场景。
32.8万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

死锁场景一:循环依赖

  1. 场景示例: 假设有两个线程 thread1thread2thread1 持有锁 lock1 并尝试获取锁 lock2,而 thread2 持有锁 lock2 并尝试获取锁 lock1。这就形成了循环依赖,导致死锁。
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let lock1 = Arc::new(Mutex::new(()));
    let lock2 = Arc::new(Mutex::new(()));

    let lock1_clone = lock1.clone();
    let lock2_clone = lock2.clone();

    let thread1 = thread::spawn(move || {
        let _guard1 = lock1_clone.lock().unwrap();
        let _guard2 = lock2_clone.lock().unwrap();
    });

    let thread2 = thread::spawn(move || {
        let _guard2 = lock2.lock().unwrap();
        let _guard1 = lock1.lock().unwrap();
    });

    thread1.join().unwrap();
    thread2.join().unwrap();
}
  1. 防范措施
    • 固定锁获取顺序:所有线程按照相同的顺序获取锁。例如,始终先获取 lock1,再获取 lock2。这样就不会出现循环依赖。
    • 使用 std::sync::TryLockError:使用 try_lock 方法尝试获取锁,如果获取失败可以进行回退或重试,而不是一直等待。例如:
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let lock1 = Arc::new(Mutex::new(()));
    let lock2 = Arc::new(Mutex::new(()));

    let lock1_clone = lock1.clone();
    let lock2_clone = lock2.clone();

    let thread1 = thread::spawn(move || {
        match (lock1_clone.try_lock(), lock2_clone.try_lock()) {
            (Ok(_guard1), Ok(_guard2)) => (),
            _ => return,
        }
    });

    let thread2 = thread::spawn(move || {
        match (lock1.try_lock(), lock2.try_lock()) {
            (Ok(_guard1), Ok(_guard2)) => (),
            _ => return,
        }
    });

    thread1.join().unwrap();
    thread2.join().unwrap();
}

死锁场景二:锁层次混乱

  1. 场景示例: 假设有三个线程 thread1thread2thread3thread1 获取锁 lock1 后调用一个函数 func1func1 又尝试获取锁 lock2thread2 获取锁 lock2 后调用 func2func2 尝试获取锁 lock3thread3 获取锁 lock3 后尝试获取锁 lock1。这种复杂的锁获取层次关系可能导致死锁。
use std::sync::{Arc, Mutex};
use std::thread;

fn func1(lock2: Arc<Mutex<()>>) {
    let _guard2 = lock2.lock().unwrap();
}

fn func2(lock3: Arc<Mutex<()>>) {
    let _guard3 = lock3.lock().unwrap();
}

fn main() {
    let lock1 = Arc::new(Mutex::new(()));
    let lock2 = Arc::new(Mutex::new(()));
    let lock3 = Arc::new(Mutex::new(()));

    let lock1_clone = lock1.clone();
    let lock2_clone = lock2.clone();
    let lock3_clone = lock3.clone();

    let thread1 = thread::spawn(move || {
        let _guard1 = lock1_clone.lock().unwrap();
        func1(lock2.clone());
    });

    let thread2 = thread::spawn(move || {
        let _guard2 = lock2_clone.lock().unwrap();
        func2(lock3.clone());
    });

    let thread3 = thread::spawn(move || {
        let _guard3 = lock3_clone.lock().unwrap();
        let _guard1 = lock1.lock().unwrap();
    });

    thread1.join().unwrap();
    thread2.join().unwrap();
    thread3.join().unwrap();
}
  1. 防范措施
    • 锁层次规划:明确锁的层次结构,确保高级别的锁不会依赖低级别的锁。例如,可以通过设计一个锁获取顺序表,所有函数和线程按照这个顺序获取锁。
    • 使用 MutexGuard 作用域:确保 MutexGuard 在函数返回前释放锁,避免在函数调用过程中锁的持有混乱。例如,将获取锁的代码块限制在最小作用域内。
use std::sync::{Arc, Mutex};
use std::thread;

fn func1(lock2: Arc<Mutex<()>>) {
    {
        let _guard2 = lock2.lock().unwrap();
        // 仅在这个块内持有锁
    }
}

fn func2(lock3: Arc<Mutex<()>>) {
    {
        let _guard3 = lock3.lock().unwrap();
        // 仅在这个块内持有锁
    }
}

fn main() {
    let lock1 = Arc::new(Mutex::new(()));
    let lock2 = Arc::new(Mutex::new(()));
    let lock3 = Arc::new(Mutex::new(()));

    let lock1_clone = lock1.clone();
    let lock2_clone = lock2.clone();
    let lock3_clone = lock3.clone();

    let thread1 = thread::spawn(move || {
        {
            let _guard1 = lock1_clone.lock().unwrap();
            func1(lock2.clone());
        }
    });

    let thread2 = thread::spawn(move || {
        {
            let _guard2 = lock2_clone.lock().unwrap();
            func2(lock3.clone());
        }
    });

    let thread3 = thread::spawn(move || {
        {
            let _guard3 = lock3_clone.lock().unwrap();
            // 这里不能再获取lock1,按照锁层次规划
        }
    });

    thread1.join().unwrap();
    thread2.join().unwrap();
    thread3.join().unwrap();
}