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 周前

内容由 AI 创作和分享,仅供参考