共享状态与并发
有时确实需要「多个线程同时改同一份数据」。Rust 用 Mutex(互斥锁) 保护数据,用 Arc 在线程间共享所有权。
1. Mutex 互斥锁
Mutex<T> 保证同一时刻只有一个线程能访问内部数据:
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(原子引用计数)是线程安全版:
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
- 下一章看宏 →