开了 deadlock_detection,程序卡死了却什么也没报
主线位置:主线 4 的一条支线。死锁检测在每次加解锁时记账,
check_deadlock()再逐桶建「资源 → 持有线程、线程 → 等待的 key」的图找环(死锁检测)。图里只有「正在停车、不带超时」的线程和记过账的锁。
flowchart TB
hang["程序卡住"] --> poll{"有线程在轮询 check_deadlock()?"}
poll -->|"否"| none1["永远不会报告"]
poll -->|"是"| kind{"卡在什么上?"}
kind -->|"两把以上 parking_lot 锁成环,都不带超时"| hit["约 65 ms 报告,线程随后永久停车"]
kind -->|"环上有 try_lock_for / wait_for"| miss1["看不到"]
kind -->|"std 锁或别的库的锁"| miss2["看不到"]
kind -->|"Condvar::wait 等不到通知"| miss3["看不到"]
kind -->|"等待链没闭合(等一个卡在 IO 上的线程)"| miss4["不是死锁,不报"]
现象
为了查一次卡死,在 Cargo.toml 里给 parking_lot 打开了 deadlock_detection,重新编译、复现,进程还是安安静静地卡着,没有任何输出。
复现
E8(exp-parking_lot/deadlock-detect,--features dd,后台每 50 ms 调一次 check_deadlock(),2 秒内看是否报告),各 3 次:
| 模式 | 构造 | 结果 |
|---|---|---|
abba | 两线程各持一把 Mutex 再拿对方的 | 65–66 ms 报告 1 个环、2 个线程 |
ring3 | 三线程 A→B、B→C、C→A | 62–67 ms 报告 1 个环、3 个线程 |
self | 一个线程对同一把 Mutex 加两次 | 60–61 ms 报告 1 个环、1 个线程 |
timed | abba,第二把锁用 try_lock_for(5 s) | 2 秒内无报告 |
std | abba,换成两把 std::sync::Mutex | 2 秒内无报告 |
chain3(反向) | A→B、B→C,C 持锁 1 秒后放开,没有环 | 无报告 |
none(反向) | 两线程依次加两把锁,不交叉 | 无报告 |
报告之后再调一次 check_deadlock(),三种能检测到的模式都返回 0 个:被报告的线程已经被标记、永久停车,不会再进图。
源码出处
- 不会自己跑:模块文档的例子就是「Create a background thread which checks for deadlocks every 10s」(
src/deadlock.rs),检测函数只是pub use parking_lot_core::deadlock::check_deadlock。 - 带超时的等待被排除:停车时记下
parked_with_timeout(core/src/parking_lot.rs:612),建图时跳过这样的线程(1268 行、1332 行),环因此断了一环。 - std 锁不经过停车场,也不调
acquire_resource,图里没有它们。 Condvar::wait等的 key 是 condvar 的地址,没有线程「持有」它,图上没有回到等待者的边。- 被报告的线程在
on_unpark里抓自己的栈发回,然后不再返回(死锁检测):检测只负责报告,不负责恢复。
怎么改
- 把它当作「锁顺序成环」的探测器,不是通用的卡死诊断。适合在测试或预发环境里开着,跑压力测试,发现两把以上 parking_lot 锁的加锁顺序问题。
- 一定要起轮询线程,并在拿到报告后打印
thread_id()和backtrace(),然后让进程退出:这些线程不会再动了。 - 其他卡死用别的工具:macOS 上
sample <pid>看所有线程的栈(卡在 parking_lot 锁上的线程栈顶是__psynch_cvwait,下一帧是lock_slow);Linux 上gdb -p或eu-stack。 - 不要在生产构建里开:它让单线程每对加解锁从 5.28 ns 变成 7.05 ns,并让每个加过锁的线程都创建
ThreadData、推动哈希表扩容(E7、E8);还和send_guard互斥(send_guard 与 deadlock_detection 冲突)。
小结
check_deadlock()要自己开线程轮询,不轮询就永远不报。- 盲区:环上有带超时的等待、std 或其他库的锁、
Condvar等待、自旋中还没停车的线程;不闭合的等待链本来就不是死锁。 - E8:两线程、三线程环和同线程重入都在 60–67 ms 内报告一次;
timed、std2 秒内无报告。 - 被报告的线程永久停车,检测只用于调试,不用于恢复。
💬 关联讨论
发起讨论加载中...