Learn
Rust/20-shared-state

共享状态与并发

有时确实需要「多个线程同时改同一份数据」。Rust 用 Mutex(互斥锁) 保护数据,用 Arc 在线程间共享所有权。

1. Mutex 互斥锁

Mutex<T> 保证同一时刻只有一个线程能访问内部数据:

Mutex 互斥锁
use std::sync::Mutex;
 
fn main() {
    let m = Mutex::new(5);
    {
        let mut num = m.lock().unwrap();   // 获取锁
        *num += 1;                          // 通过解引用修改
    }                                        // 离开作用域自动释放锁
    println!("{}", m.lock().unwrap());
}
⚠️锁要尽快释放

lock() 返回守卫(guard),其作用域结束即自动解锁。务必把修改逻辑包在小作用域里,别长时间持锁,否则阻塞其他线程。

2. Arc 跨线程共享

Rc 不是 Send,不能跨线程。Arc(原子引用计数)是线程安全版:

Arc + 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..3 {
        let c = Arc::clone(&counter);
        let h = thread::spawn(move || {
            let mut num = c.lock().unwrap();
            *num += 1;
        });
        handles.push(h);
    }
 
    for h in handles { h.join().unwrap(); }
    println!("结果 = {}", *counter.lock().unwrap());   // 3
}
ℹ️Arc<Mutex<T>> 是经典组合

Arc 解决「多所有者共享」,但只读;Mutex 解决「内部可变但互斥」。二者结合 = 多线程安全修改共享状态。

3. Send 与 Sync

这两个 marker trait 决定了类型能否跨线程:

  • Send:所有权可以转移到另一线程。
  • Sync:可以被多个线程同时「共享引用」。
💡编译器替你守门

绝大多数标准库类型自动实现了 Send/Sync。Rc 不是 Send(所以不能在 spawn 里用),而 Arc 是。若你写并发类型,编译器会强制你满足这些约束。

4. 死锁警示

两个线程各持一把锁、又互等对方的锁,会死锁:

// 错误示意:先锁 a 再锁 b,另一线程先锁 b 再锁 a → 可能互等

写代码时固定加锁顺序,或使用 Mutex 粒度最小化来规避。

⚠️加锁顺序要一致

跨线程获取多把锁时,所有线程必须用相同顺序,否则易出现死锁。能用 channel 就优先用 channel。

🎯练习

用 Arc<Mutex<Vec<i32>>> 起 4 个线程,每个线程向向量 push 自己线程编号(0..3),最后主线程打印长度应为 4 且包含全部编号。体会锁的互斥性。

小结

  • Mutex 互斥访问内部数据,guard 离开作用域自动解锁
  • Arc 是线程安全的引用计数,配合 Mutex 共享可变状态
  • Send/Sync 由编译器强制,保证跨线程安全
  • 加锁顺序一致防死锁;优先 channel
  • 下一章看宏 →