线程间同步机制设计
- 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();
- 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();
死锁产生原因
- 循环依赖:例如线程A持有锁1,等待获取锁2,而线程B持有锁2,等待获取锁1,形成循环等待,导致死锁。
- 锁获取顺序不一致:假设有两个锁
lock1
和lock2
,线程1先获取lock1
再获取lock2
,而线程2先获取lock2
再获取lock1
。如果线程1获取了lock1
,线程2获取了lock2
,此时它们相互等待对方释放锁,就会产生死锁。
预防策略原理
- 固定锁获取顺序:确保所有线程以相同的顺序获取锁。例如,总是先获取锁1,再获取锁2。这样就不会出现循环依赖导致的死锁。如果所有线程都按照这个顺序获取锁,当一个线程获取了锁1后,其他线程必须等待它释放锁1才能获取锁1进而获取锁2,避免了死锁。
- 资源分配图算法:在复杂场景下,可以使用资源分配图算法(如银行家算法的变体)来检测和避免死锁。它通过跟踪资源的分配和请求情况,确保系统不会进入死锁状态。当一个线程请求资源时,算法检查该请求是否会导致系统进入不安全状态(可能导致死锁),如果会,则拒绝请求,直到系统处于安全状态。
- 超时机制:在获取锁时设置超时时间。如果在规定时间内没有获取到锁,线程可以放弃当前操作并释放已获取的锁,然后重试或者执行其他操作。例如使用
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; }
。这样可以避免线程无限期等待锁,从而防止死锁。