死锁排查
问题
Rust 中会发生死锁吗?如何排查和预防?
答案
Rust 的类型系统可以防止数据竞争,但不能防止死锁。死锁是逻辑问题,不是内存安全问题。
常见死锁场景
1. 锁顺序不一致
use std::sync::Mutex;
let lock_a = Mutex::new(1);
let lock_b = Mutex::new(2);
// 线程 1:先锁 A,再锁 B
let _a = lock_a.lock().unwrap();
let _b = lock_b.lock().unwrap(); // 等待 B
// 线程 2:先锁 B,再锁 A
let _b = lock_b.lock().unwrap();
let _a = lock_a.lock().unwrap(); // 等待 A → 死锁!
// ✅ 修复:统一锁顺序(总是先锁 A)
2. 异步代码中的死锁
use std::sync::Mutex; // 标准库的 Mutex
async fn deadlock_risk(data: &Mutex<Vec<i32>>) {
let guard = data.lock().unwrap();
// 如果在持有 guard 时 .await,当前 task 被挂起
// 但 Mutex guard 未释放
// 其他 task 无法获取锁
some_async_operation().await; // ← 持有锁跨 await
drop(guard);
}
// ✅ 修复:在 .await 之前释放锁
async fn safe(data: &Mutex<Vec<i32>>) {
let value = {
let guard = data.lock().unwrap();
guard.clone() // 拷贝后立即释放锁
};
some_async_operation().await;
}
3. 同一线程重复获取非可重入锁
let lock = Mutex::new(0);
let _guard1 = lock.lock().unwrap();
let _guard2 = lock.lock().unwrap(); // 死锁!同一线程重复锁
// ✅ 修复:重构代码,避免重复锁
// 或使用 parking_lot::ReentrantMutex
死锁排查方法
| 方法 | 工具 |
|---|---|
| 线程转储 | gdb / lldb 查看线程栈 |
| 锁依赖图 | 手动分析或 no-deadlocks crate |
| Sanitizer | RUSTFLAGS="-Zsanitizer=thread" |
| 超时检测 | parking_lot::Mutex 的 try_lock_for |
常见面试问题
Q1: 如何预防 Rust 中的死锁?
答案:
- 统一锁顺序:全局定义锁的获取顺序
- 缩小锁范围:尽快释放锁,不跨 .await
- 使用
tokio::sync::Mutex:在异步代码中,它可以跨 await - 超时:
try_lock_for设置获取锁的超时时间 - 减少锁使用:用 Channel、
Arc<AtomicXxx>等无锁方案