跳到主要内容

Mutex 与 RwLock

问题

Rust 的 MutexRwLock 与其他语言有何不同?如何避免死锁?

答案

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 中也是可以的,甚至性能更好。

相关链接