10.3 共享状态并发:Mutex 与 Arc
听
尽管消息传递是 Rust 推荐的并发模式,但在某些场景下,多个线程需要共享并修改同一份数据。此时,Rust 提供了基于互斥锁(Mutex)和原子引用计数(Arc)的组合方案,以安全地实现共享状态并发。
使用 Mutex<T> 保护共享数据
Mutex<T>(“Mutual Exclusion”)确保同一时间只有一个线程能访问其内部数据。任何对内部值的访问都必须先获取锁:
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
然而,上述代码无法编译。问题在于:每个线程尝试捕获 counter,但 Mutex 本身不支持跨线程共享——它没有实现 Sync trait 的完整组合。我们需要一种方式让多个线程安全地共享同一个 Mutex 实例。
引入 Arc<T>:线程安全的引用计数
Arc<T>(Atomically Reference Counted)是 Rc<T> 的线程安全版本,允许多个所有者跨线程共享同一数据。将 Mutex 包裹在 Arc 中,即可解决上述问题:
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_clone = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter_clone.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap()); // 输出: 10
}
这里:
Arc::new(...)创建一个堆上分配的Mutex<i32>;- 每个线程通过
Arc::clone()获得指向同一Mutex的新引用; lock()返回一个MutexGuard,它在离开作用域时自动释放锁;- 所有操作均在编译期和运行时受到安全检查。
锁的注意事项
- 死锁风险:若一个线程已持有锁,再次调用
lock()会导致死锁(Rust 的Mutex是不可重入的); - 中毒(Poisoning):如果持有锁的线程在临界区内 panic,
Mutex会被标记为“中毒”,后续lock()返回Err。通常可调用.unwrap()忽略(因 panic 已表明程序异常),或显式处理; - 性能开销:锁会引入同步成本,高竞争场景下可能成为瓶颈。
适用场景
Arc<Mutex<T>> 常用于:
- 全局配置或缓存的共享更新;
- 多线程任务池中的工作队列状态;
- 需要精确控制共享可变状态的系统组件。
小结
Mutex<T> 与 Arc<T> 的组合是 Rust 中实现共享状态并发的标准方式。Arc 解决了多线程间的所有权共享问题,Mutex 则保证了对内部数据的互斥访问。虽然这种模式比消息传递更易引入竞争和死锁,但在合理使用下,仍能构建出高效且内存安全的并发程序。理解其工作机制,是掌握 Rust 并发编程的关键一环。
#Rust 入门教程
分享于 1 周前