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_thread | parking_lot | 3/3 卡死,看门狗 2.00–2.01 s 触发 |
current_thread(反向) | tokio::sync::Mutex | 3/3 正常,B 等了 15.7–16.5 ms |
| 多线程 2 个 worker(反向) | parking_lot | 3/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运行时持锁跨.await3/3 卡死;tokio::sync::Mutex、多线程运行时作为反向验证都能结束。 - 有
.await用异步锁;没有就让守卫在 await 之前结束。
💬 关联讨论
发起讨论加载中...