Mutex 与 RwLock
问题
Rust 的 Mutex 和 RwLock 与其他语言有何不同?如何避免死锁?
答案
Mutex 基础
Rust 的 Mutex 拥有数据——数据被锁保护,只能通过获取锁来访问:
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
// lock() 返回 MutexGuard,自动解锁(RAII)
let mut num = m.lock().unwrap();
*num = 6;
} // MutexGuard drop,自动释放锁
println!("{:?}", m); // Mutex { data: 6 }
}
与 C/C++ 的区别:C++ 锁和数据分离,你可能忘记加锁就访问数据。Rust 的 Mutex<T> 强制必须通过锁才能访问 T。
多线程共享 Mutex
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
}));
}
for h in handles { h.join().unwrap(); }
println!("结果: {}", *counter.lock().unwrap()); // 10
}
中毒(Poisoning)
当持有锁的线程 panic 时,锁会"中毒":
let lock = Arc::new(Mutex::new(vec![]));
// 线程 panic 导致锁中毒
let _ = thread::spawn({
let lock = lock.clone();
move || {
let mut data = lock.lock().unwrap();
data.push(1);
panic!("出错了!");
}
}).join();
// 后续 lock() 返回 Err(PoisonError)
match lock.lock() {
Ok(data) => println!("{:?}", data),
Err(poisoned) => {
// 仍可恢复数据
let data = poisoned.into_inner();
println!("中毒但恢复: {:?}", data);
}
}
RwLock
允许多个读者 或 一个写者:
use std::sync::RwLock;
let lock = RwLock::new(5);
// 多个读者可以同时持有
{
let r1 = lock.read().unwrap();
let r2 = lock.read().unwrap(); // ✅ 同时读
println!("{} {}", r1, r2);
}
// 写者独占
{
let mut w = lock.write().unwrap();
*w += 1;
}
| 特性 | Mutex<T> | RwLock<T> |
|---|---|---|
| 并发读 | ❌ 互斥 | ✅ 多读者 |
| 并发写 | ❌ 互斥 | ❌ 独占 |
| 适用场景 | 写多读少 | 读多写少 |
| 开销 | 较低 | 较高(读写锁逻辑复杂) |
避免死锁
// ❌ 可能死锁:两个线程以不同顺序获取锁
let lock_a = Mutex::new(1);
let lock_b = Mutex::new(2);
// 线程1: lock_a → lock_b
// 线程2: lock_b → lock_a → 死锁!
// ✅ 解决:总是按固定顺序获取锁
// 或使用 try_lock 避免阻塞
if let Ok(a) = lock_a.try_lock() {
if let Ok(b) = lock_b.try_lock() {
// 同时持有两个锁
}
}
常见面试问题
Q1: Rust 的 Mutex 与 C++ 的 mutex 有什么本质区别?
答案:
Rust 的 Mutex<T> 拥有并保护数据——你不可能在不获取锁的情况下访问内部数据。C++ 的 std::mutex 只是一把锁,与数据分离,程序员可能忘记在访问共享数据前加锁。
Q2: lock() 和 try_lock() 的区别?
答案:
lock():阻塞等待直到获取锁,返回Result<MutexGuard, PoisonError>try_lock():尝试获取锁,如果已被占用立即返回Err(TryLockError::WouldBlock)
try_lock 适用于避免死锁或实现超时逻辑。
Q3: 什么时候用 Mutex,什么时候用 RwLock?
答案:
- Mutex:写操作频繁,或者读写比例接近。Mutex 逻辑更简单,开销更低
- RwLock:读操作远多于写操作的场景。如果 95% 是读,RwLock 允许并发读,吞吐量更高
如果不确定,优先用 Mutex——更简单且足够好。
Q4: 为什么 Rust 标准库没有 async Mutex?
答案:
标准库的 Mutex 会阻塞线程。在 async 代码中阻塞会占用运行时线程。应使用 tokio::sync::Mutex——它在等待锁时让出线程给其他任务。
但如果锁持有时间非常短(不跨 await),用标准库 Mutex 在 async 中也是可以的,甚至性能更好。