并发与异步安全
原理
数据竞争(data race)在 Rust 安全代码中被消除:Send + Sync trait 保证编译期检查。Send = 可跨线程传递所有权;Sync = 可跨线程共享 &T。
常见陷阱:
- 死锁(deadlock):两个
Mutex互相等待。解决方案:始终以相同顺序获取锁 - 活锁(livelock):两个线程不断重试,无法实际进展
- 竞态条件(race condition):逻辑顺序依赖执行时序——Rust 无法完全防止(需业务层设计)
tokio::spawn 的 Future 必须为 Send(因为工作窃取调度器在线程间移动 task)。非 Send 的 Future 可用 LocalSet 在单线程上运行。
取消安全(cancellation safety):tokio::select! 在一分支就绪时取消其他分支,需确保被取消的 Future 不丢失已处理的状态。典型模式:使用 tokio::sync::oneshot 或 async fn 返回 Result 表示完整性。
Mutex<T> 建议搭配 Arc 在多线程中使用;RwLock<T> 适合读多写少场景,但要注意 writer starvation。
语法
use std::sync::{Arc, Mutex};
use tokio::sync::RwLock;
// 死锁预防 — 锁顺序
fn transfer(
from: &Mutex<Account>,
to: &Mutex<Account>,
amount: u64,
) -> Result<(), Error> {
let (first, second) = if from.lock().unwrap().id < to.lock().unwrap().id {
(from, to)
} else {
(to, from)
};
let mut f = first.lock().unwrap();
let mut t = second.lock().unwrap();
f.balance -= amount;
t.balance += amount;
Ok(())
}
// tokio select — 注意取消安全
tokio::select! {
result = async_op() => { /* 处理结果 */ }
_ = tokio::signal::ctrl_c() => { println!("shutdown"); }
_ = tokio::time::sleep(Duration::from_secs(1)) => { /* timeout */ }
}
// 优雅关闭
use tokio::sync::Notify;
let shutdown = Arc::new(Notify::new());
tokio::select! {
_ = shutdown.notified() => { /* shutdown */ }
_ = worker() => { }
}并发模型对比
| std::thread | tokio::task | rayon | |
|---|---|---|---|
| 并发模型 | OS 线程 | 协作式协程 | work-stealing 线程池 |
| 适合场景 | CPU 密集 | IO 密集 | 数据并行 |
| 切换开销 | ~1μs | ~20ns | 按任务大小 |
| 并发数 | 数百 | 百万 | 数百(按核) |
实践
AI 自检
tokio::select!中的 cancellation safety 问题如何导致数据丢失?举一个具体 Future 例子。RwLock的 writer starvation 问题如何产生?tokio::sync::RwLock如何缓解?