并发与异步安全

原理

数据竞争(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::oneshotasync 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::threadtokio::taskrayon
并发模型OS 线程协作式协程work-stealing 线程池
适合场景CPU 密集IO 密集数据并行
切换开销~1μs~20ns按任务大小
并发数数百百万数百(按核)

实践

AI 自检

  1. tokio::select! 中的 cancellation safety 问题如何导致数据丢失?举一个具体 Future 例子。
  2. RwLock 的 writer starvation 问题如何产生?tokio::sync::RwLock 如何缓解?