tokio::sync::Mutex 与 std::sync::Mutex

📅 2026/7/24 21:55:05
tokio::sync::Mutex 与 std::sync::Mutex
代码useanyhow::{Ok,Result};usestd::{ptr::read,sync::Arc,time::Duration};usetokio::sync::Mutex;structDB;implDB{// 假装在 commit 数据asyncfncommit(mutself)-Resultusize{Ok(42)}}#[tokio::main]asyncfnmain()-Result(){letdb1Arc::new(Mutex::new(DB));letdb2Arc::clone(db1);tokio::spawn(asyncmove{letmutdbdb1.lock().await;// 因为拿到的 MutexGuard 要跨越 await , 所以不能用 std::sync::Mutex// 只能用 tokio::sync::Mutexletaffecteddb.commit().await?;println!(db1: Total affected rows: {},affected);letresult:Result_,anyhow::ErrorOk(());result});tokio::spawn(asyncmove{letmutdbdb2.lock().await;letaffecteddb.commit().await?;println!(db2: Total affected rows: {},affected);letresult:Result_,anyhow::ErrorOk(());result});// 让两个task 有机会执行完tokio::time::sleep(Duration::from_millis(1)).await;Ok(())}在异步 Rust 中使用tokio::sync::Mutex而不是std::sync::Mutex的核心原因是std::sync::MutexGuard不是Send的, 而tokio的MutexGuard是Send的。为什么普通的 MutexGuard 编译会出错工作窃取work-stealing调度tokio默认使用多线程运行时任务Future可能在线程之间迁移被窃取。这意味着一个 Future 可能在线程 A 上开始执行遇到 .await 后挂起然后被调度到线程 B 上继续执行。std::sync::MutexGuard 不是 Sendstd::sync::MutexGuard 内部包含对当前线程特定资源的引用它被设计为不能安全地在线程间传递即它没有实现 Send trait。如果你的代码在 .await 之前获取了 std::sync::MutexGuard然后在 .await 之后继续使用它那么在.await点任务可能被调度到另一个线程。此时MutexGuard会随任务一起被“移动”到新线程但MutexGuard不是Send这违反了 Rust 的所有权规则编译器会直接报错。编译器错误示例usestd::sync::Mutex;asyncfnexample(){letlockMutex::new(0);letguardlock.lock().unwrap();// 获取 MutexGuardtokio::time::sleep(Duration::from_secs(1)).await;// .await 点println!({},*guard);// 这里可能会在不同线程继续执行}编译器会报告类似error: future cannot be sent between threads safely note: std::sync::MutexGuard_, u32 is not Sendtokio::sync::Mutex 的解决方案tokio::sync::Mutex::lock() 返回一个 Future它会异步等待锁不会阻塞当前线程。它返回的 MutexGuard 实现了 Send因此可以安全地跨 .await 点被传递到其他线程。这样你就可以在异步函数中安全地持有一个锁并跨越 .await 点。注意事项性能tokio::sync::Mutex 比 std::sync::Mutex 有更高的开销因为它需要异步等待和唤醒机制。使用场景如果临界区很短并且不需要跨越 .await建议使用 std::sync::Mutex因为它更快且无异步开销。如果需要在 .await 期间保持锁如你的例子中db.commit().await 在锁内执行则必须使用 tokio::sync::Mutex。