Rust in Everything
基于 parking_lot 0.12.5

future cannot be sent between threads safely:*mut () 不是 Send

主线位置:主线 1 的出口。守卫析构时调 raw.unlock(),lock_api 用 GuardMarker 把「能否在别的线程解锁」写进守卫类型(GuardSend 与 send_guard)。tokio 的多线程 spawn 要求 future 是 Send,持着守卫跨 .await 的 future 就不是。

flowchart TB
  code["async 块里 let g = m.lock(); ...; x.await"] --> st["守卫活过 await,成为 future 状态的一部分"]
  st --> mk["MutexGuard 含 PhantomData<GuardNoSend>"]
  mk --> ptr["GuardNoSend(*mut ()):不是 Send"]
  ptr --> err["tokio::spawn 要求 F: Send,编译失败"]
  err -->|"开 send_guard"| ok["能编译"]
  ok --> hang["锁阻塞的是 OS 线程:current_thread 运行时卡死"]

报错原文

E9 ①(exp-parking_lot/guard-await,parking_lot 0.12.5 + tokio 1.54.1,默认 feature,cargo check,路径已缩短):

error: future cannot be sent between threads safely
   --> src/bin/compile.rs:11:11
    |
 11 |     let h = tokio::spawn(async move {
    |  ___________^
 12 | |     let mut g = m2.lock();
 13 | |     tokio::time::sleep(Duration::from_millis(10)).await;
 14 | |     *g += 1;
 15 | |   });
    | |____^ future created by async block is not `Send`
    |
    = help: within `{async block@src/bin/compile.rs:11:24: 11:34}`, the trait `Send` is not implemented for `*mut ()`
note: future is not `Send` as this value is used across an await
   --> src/bin/compile.rs:13:51
    |
 12 |     let mut g = m2.lock();
    |         ----- has type `parking_lot::lock_api::MutexGuard<'_, parking_lot::RawMutex, u32>` which is not `Send`

help 行只说 *mut (),代码里根本没有裸指针,容易让人往 Rc、FFI 的方向去找。真正的线索在 note 行:守卫类型 MutexGuard<'_, parking_lot::RawMutex, u32>。

源码出处

  • lock_api/src/lib.rs:99-106:pub struct GuardNoSend(*mut ());,再单独 unsafe impl Sync。*mut () 就是编译器在推导链最底层找到的那个不满足 Send 的类型。
  • src/lib.rs:32-39:没开 send_guard 时 type GuardMarker = lock_api::GuardNoSend;,RawMutex / RawRwLock 都用它。
  • 守卫的 marker: PhantomData<(&'a mut T, R::GuardMarker)>(lock_api/src/mutex.rs:504-507)把标记的 !Send 传给守卫。

开了 send_guard 之后

E9 ②:开 send_guard,任务 A 持锁 sleep(10ms).await,任务 B 再 lock() 同一把锁,2 秒看门狗,每种 3 次:

运行时锁结果
current_threadparking_lot3/3 卡死,看门狗 2.00–2.01 s 触发
current_thread(反向)tokio::sync::Mutex3/3 正常,B 等了 15.7–16.5 ms
多线程 2 个 worker(反向)parking_lot3/3 正常,B 等了 12.6–16.5 ms,期间占住一个 worker

B 的 lock() 在停车场里睡下,把运行时唯一的线程带走,A 的定时器没人推进,A 永远不释放锁。编译器不再拦你,但阻塞问题照旧。多线程运行时下不会死,代价是被阻塞的 worker 上排队的任务都在等。

怎么改

  • 临界区里有 .await 就用 tokio::sync::Mutex:lock().await 拿不到时让出线程。两种锁的开销和选择表在 tokio 卷 tokio 的 Mutex 还是 std 的 Mutex。
  • 临界区里没有 .await 就在 await 前放掉守卫:把守卫关进一个块,{ let mut g = m.lock(); *g += 1; },块结束守卫就析构,后面的 .await 不再持有它。
  • 不要为了消掉这个错误去开 send_guard:它是全图统一的 feature,一开就改了整个程序里所有 parking_lot 守卫的类型,还和 deadlock_detection 互斥(下一页)。
  • 同样的错误、std 守卫的版本和几种改法见 tokio 卷 future cannot be sent between threads safely。

小结

  • 报错的 help 行说 *mut (),来自 lock_api 的 GuardNoSend(*mut ());note 行才给出守卫类型。
  • parking_lot 守卫默认 !Send,send_guard 能让它编译通过。
  • E9:开 send_guard 后 current_thread 运行时持锁跨 .await 3/3 卡死;tokio::sync::Mutex、多线程运行时作为反向验证都能结束。
  • 有 .await 用异步锁;没有就让守卫在 await 之前结束。

💬 关联讨论

发起讨论
加载中...