外观
第 3 节:锁中毒——线程 panic 后,锁会"变味"
症状
一个线程拿着锁干活时 panic! 了,之后其他线程 lock() 全部返回 Err——锁好像"坏"了。
诊断
锁中毒(mutex poisoning):Mutex 的设计哲学——"数据可能已经坏掉了"。
想想:线程 A 拿着锁,正要改数据,改到一半 panic 了——锁自动释放(第 15 章 guard 离开作用域自动解锁),但数据可能停在"改了一半"的中间状态。其他线程拿到锁,读到的是半成品数据。
Rust 的选择:不假装没事——把这种锁标记为中毒(poisoned)。之后 lock() 返回 Err(PoisonError),提醒你"这里出过事,数据可能不可信"。
实测:
运行输出
text
thread '<unnamed>' panicked at src/main.rs:12:9:
这个线程炸了!
锁中毒了!里面的值是 1线程 1 加锁后 panic;线程 2 lock() 拿到 Err,通过 into_inner() 还能抢救出里面的值(1)。
解药
解药一:确认数据没坏,继续用(unwrap)——panic 发生在"加锁后立刻 return"之类的安全点:
rust
let mut guard = counter.lock().unwrap(); // 中毒?我们认为数据没问题,继续解药二:拿到中毒锁,先检查数据完整性,再决定——真实项目的稳健姿势:
rust
match counter.lock() {
Ok(guard) => { /* 正常 */ }
Err(poisoned) => {
// 数据可能半成品:记录日志、重置数据,或者用 into_inner 抢救
let value = poisoned.into_inner();
eprintln!("警告:数据可能不完整,当前值 {}", value);
}
}解药三:禁止线程 panic(更好的预防)——锁里的代码别 panic:
rust
// 危险:锁里可能 panic(索引越界、unwrap)
let mut guard = data.lock().unwrap();
*guard = items[99]; // 越界就 panic,锁中毒!
// 好:锁外检查,锁内只做安全的赋值
if items.len() > 99 {
let mut guard = data.lock().unwrap();
*guard = items[99];
}预防
- 锁中毒是"数据可能半成品"的警报器,不是 bug 本身:真正的 bug 是"在锁里 panic"
- 锁里的代码要"不会 panic":不 unwrap、不索引越界、不调用可能 panic 的逻辑——锁里只做简单可靠的操作
.lock().unwrap()的 unwrap 意义:成功 → 用;中毒 → 崩溃(程序宁死不读坏数据)——对"数据必须一致"的场合,这反而是对的- 测试里也会触发:测试线程 panic 会让锁中毒,
cargo test里handle.join().unwrap()拿到Err——join的Err也是"线程 panic"的信号,别忽略