死锁产生原因
- 循环依赖:多个线程互相持有对方需要的锁,形成循环等待。例如,线程A持有锁L1并等待锁L2,而线程B持有锁L2并等待锁L1。
- 锁获取顺序不一致:不同线程以不同顺序获取多个锁,可能导致死锁。比如,线程1按顺序获取锁A和锁B,线程2按顺序获取锁B和锁A。
饥饿产生原因
- 高优先级线程长期占用资源:如果系统存在线程优先级机制,高优先级线程持续获取锁并长时间持有,低优先级线程可能长时间无法获取锁,从而产生饥饿。
- 不公平的锁分配策略:某些锁的实现采用不公平策略,新请求的线程可能优先于等待队列中的线程获取锁,导致等待时间长的线程一直无法获取锁。
预防死锁思路
- 固定锁获取顺序:所有线程都按照相同顺序获取多个锁,避免因获取顺序不一致导致死锁。例如,始终先获取锁A,再获取锁B。
- 使用
try_lock
方法:尝试获取锁,如果获取失败(锁已被占用),则不进入等待状态,而是执行其他操作,稍后再尝试。例如:
use std::sync::{Mutex, TryLockError};
let mutex = Mutex::new(0);
match mutex.try_lock() {
Ok(_guard) => {
// 成功获取锁,执行需要临界区访问的代码
}
Err(TryLockError::WouldBlock) => {
// 锁已被占用,执行其他操作
}
Err(TryLockError::Poisoned) => {
// 锁被毒死,通常意味着持有锁的线程发生了恐慌
}
}
检测死锁思路及工具
- 使用
deadlock
crate:这个crate可以检测程序中的死锁情况。它通过在后台线程中定期检查线程的锁持有状态和等待关系,判断是否存在死锁。例如:
use deadlock::deadlock;
fn main() {
deadlock(|| {
// 这里放置可能出现死锁的代码
});
}
避免饥饿思路及工具
- 使用公平锁:Rust标准库中
parking_lot
crate提供了公平的Mutex
和RwLock
实现。公平锁按照线程等待顺序分配锁,避免新线程插队,从而减少饥饿现象。例如:
use parking_lot::Mutex;
let mutex = Mutex::new(0);
{
let mut data = mutex.lock();
// 临界区代码
}
- 调整线程优先级:在支持线程优先级的系统中,可以适当调整线程优先级,避免高优先级线程长期占用资源。例如,在Linux系统下,可以使用
libc
crate来设置线程优先级。不过Rust标准库本身不直接提供跨平台的线程优先级设置功能。