MST

星途 面试题库

面试题:Rust并发编程中如何有效避免死锁

在一个Rust多线程并发程序中,存在多个线程需要访问和修改共享资源。请阐述你会如何设计线程间的同步机制,例如使用Mutex、RwLock等,以避免死锁情况的发生。并且结合具体场景,说明死锁产生的原因以及你采取的预防策略的原理。
48.2万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

线程间同步机制设计

  1. Mutex(互斥锁)
    • 适用场景:当只有一个线程需要对共享资源进行读写操作时,使用Mutex。例如,多个线程可能会访问一个全局计数器,对其进行递增操作。
    • 使用方式:在Rust中,通过std::sync::Mutex来创建互斥锁。首先,将共享资源包裹在Mutex中,如let counter = Mutex::new(0);。然后在线程中获取锁才能访问和修改资源,例如:
use std::sync::Mutex;
use std::thread;

let counter = Mutex::new(0);
let handle = thread::spawn(move || {
    let mut num = counter.lock().unwrap();
    *num += 1;
});
handle.join().unwrap();
  1. RwLock(读写锁)
    • 适用场景:当有多个线程需要读共享资源,而只有少数线程需要写共享资源时,使用RwLock。比如一个配置文件,多个线程可能读取配置信息,但只有特定的管理线程会修改配置。
    • 使用方式:在Rust中,通过std::sync::RwLock来创建读写锁。将共享资源包裹在RwLock中,如let config = RwLock::new(Config::default());。读操作获取读锁,写操作获取写锁,示例如下:
use std::sync::RwLock;
use std::thread;

let config = RwLock::new(Config::default());
let read_handle = thread::spawn(move || {
    let conf = config.read().unwrap();
    // 读取配置信息
});
let write_handle = thread::spawn(move || {
    let mut conf = config.write().unwrap();
    // 修改配置信息
});
read_handle.join().unwrap();
write_handle.join().unwrap();

死锁产生原因

  1. 循环依赖:例如线程A持有锁1,等待获取锁2,而线程B持有锁2,等待获取锁1,形成循环等待,导致死锁。
  2. 锁获取顺序不一致:假设有两个锁lock1lock2,线程1先获取lock1再获取lock2,而线程2先获取lock2再获取lock1。如果线程1获取了lock1,线程2获取了lock2,此时它们相互等待对方释放锁,就会产生死锁。

预防策略原理

  1. 固定锁获取顺序:确保所有线程以相同的顺序获取锁。例如,总是先获取锁1,再获取锁2。这样就不会出现循环依赖导致的死锁。如果所有线程都按照这个顺序获取锁,当一个线程获取了锁1后,其他线程必须等待它释放锁1才能获取锁1进而获取锁2,避免了死锁。
  2. 资源分配图算法:在复杂场景下,可以使用资源分配图算法(如银行家算法的变体)来检测和避免死锁。它通过跟踪资源的分配和请求情况,确保系统不会进入死锁状态。当一个线程请求资源时,算法检查该请求是否会导致系统进入不安全状态(可能导致死锁),如果会,则拒绝请求,直到系统处于安全状态。
  3. 超时机制:在获取锁时设置超时时间。如果在规定时间内没有获取到锁,线程可以放弃当前操作并释放已获取的锁,然后重试或者执行其他操作。例如使用try_lock方法并结合std::time::Duration设置超时,如if let Ok(mut num) = counter.try_lock().unwrap_or_else(|e| { if e.is_timeout() { return; } e.into_inner() }) { *num += 1; }。这样可以避免线程无限期等待锁,从而防止死锁。