Mutex 是线程之间「共享且可修改」的标准方案,它把锁和数据绑在一起 —— 不拿锁就拿不到数据。这篇验证 guard 的生命周期、锁中毒的行为、两种真实的死锁写法(包括一个很隐蔽的),以及持锁时间对并发度的影响。
源码:
1 | use std::sync::Mutex;
2 | fn main() {
3 | let m = Mutex::new(vec![1, 2, 3]); // m 不是 mut
4 | {
5 | let mut g = m.lock().unwrap(); // 拿到 guard
6 | g.push(4);
7 | println!("持有 guard 时 try_lock() 失败:{}", m.try_lock().is_err());
8 | } // guard 在这里 drop,自动解锁
9 | let free = m.try_lock().is_ok(); // 单独一条语句:临时的 Result<MutexGuard> 在语句结束时释放
10 | println!("guard 释放之后 try_lock() 成功:{},值 = {:?}", free, m.lock().unwrap());
11 | *m.lock().unwrap() = vec![9]; // 临时 guard,语句结束就释放
12 | println!("{:?}", m.lock().unwrap());
13 | }
编译:成功
运行(退出码 0):
持有 guard 时 try_lock() 失败:true
guard 释放之后 try_lock() 成功:true,值 = [1, 2, 3, 4]
[9] 源码:
1 | use std::sync::{Arc, Mutex};
2 | use std::thread;
3 | fn main() {
4 | let m = Arc::new(Mutex::new(5));
5 | let m2 = Arc::clone(&m);
6 | let h = thread::spawn(move || { let _g = m2.lock().unwrap(); panic!("持锁时 panic"); });
7 | println!("子线程的 join 结果是 Err:{}", h.join().is_err());
8 | println!("is_poisoned() = {}", m.is_poisoned());
9 | match m.lock() {
10 | Ok(g) => println!("拿到了 {}", *g),
11 | Err(e) => { println!("lock() 返回 Err(PoisonError);里面的数据仍然可以取出:{}", *e.into_inner()); }
12 | }
13 | }
编译:成功
运行(退出码 0):
子线程的 join 结果是 Err:true
is_poisoned() = true
lock() 返回 Err(PoisonError);里面的数据仍然可以取出:5
thread '<unnamed>' (7002078) panicked at <实验目录>/src/u34b.rs:6:66:
持锁时 panic
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 程序 u34c:
1 | use std::sync::Mutex;
2 | fn main() {
3 | let m = Mutex::new(0);
4 | let _a = m.lock().unwrap();
5 | println!("第一次 lock 成功,准备第二次 lock ...");
6 | let _b = m.lock().unwrap();
7 | println!("不会打印");
8 | }
3 秒后仍未结束(被超时杀掉);已有的输出: '第一次 lock 成功,准备第二次 lock ...'
程序 u34c2:
1 | use std::sync::Mutex;
2 | fn main() {
3 | let m = Mutex::new(0);
4 | // try_lock() 返回的 Result<MutexGuard> 是临时值,要到整条 println! 语句结束才 drop
5 | println!("{} {}", m.try_lock().is_ok(), m.lock().unwrap());
6 | }
3 秒后仍未结束(被超时杀掉);已有的输出: ''临时值的存活范围在 edition 2024 的 if let 里有变化(见if let 与 let else)。
编译:成功
运行(退出码 0):
4 个线程各在锁内睡 50 ms:总耗时约 229 ms(串行,≈ 4 × 50)
4 个线程在锁外睡 50 ms、只在锁内加 1:总耗时约 60 ms(并行,≈ 50)读多写少时可以用 RwLock,见Mutex 与 RwLock。
编译:成功
运行(退出码 0):
u32 = 4,Mutex<u32> = 16