parking_lot 源码解析
全卷对着一个 tag 写:
| crate | 版本 | 源码链接用的 tag |
|---|---|---|
parking_lot | 0.12.5 | parking_lot-v0.12.5(提交 d7828ff,「chore: release」,2025-09-30) |
parking_lot_core | 0.9.12 | 同上;tag parking_lot_core-v0.9.12 指向同一个提交 |
lock_api | 0.4.14 | 同上;tag lock_api-v0.4.14 指向同一个提交 |
三个 crate 在同一个仓库、同一次发布。0.13.0(2026-10-21)改动很大,正文只写 0.12.5,差别集中在 07 0.13 的变化。
parking_lot 是一套比标准库更小、更可定制的同步原语。用户看到的是 Mutex、RwLock、Condvar、Once,底下分三层:
- parking_lot_core:一个全局「停车场」。任意地址都可以当作一条等待队列的 key,线程按 key 停车(
park)、按 key 被叫醒(unpark_*),所有队列放在一张全局哈希表里。这一层不知道锁是什么,只提供「在桶锁里检查条件、确认后再睡」的协议。 - lock_api:锁的类型安全外壳。
unsafe trait RawMutex/RawRwLock描述「一把原始锁」,Mutex<R, T>、RwLock<R, T>和各种守卫与具体实现无关,别的 crate 也用它包自己的锁。 - parking_lot:用前两层实现的
RawMutex(1 字节状态)、RawRwLock(1 个字)、Condvar(1 个指针)、Once(1 字节),以及ReentrantMutex、FairMutex和死锁检测的入口。
它和前几卷的关系:crossbeam 卷 是直接前置,那里讲过的 Parker、Backoff、CachePadded、内存序分类,本卷只做对照、链过去。rayon 卷 的 sleep 模块用 std 的 Mutex + Condvar 做「不丢唤醒」,和停车场的 validate 是同一个思路。tokio 卷 的 parking_lot feature、dioxus 卷 的 SyncStorage、probe-rs 卷 的 FairMutex、tracing 卷 的 reload 层都直接用到 parking_lot,本卷用源码解释它们的行为。
全卷沿四条线展开:
- 一次有竞争的
lock/unlock:快路径一次 CAS;失败后自旋、置PARKED_BIT、在停车场里停车;解锁方在桶锁里决定是直接把锁交给被叫醒者,还是放开锁让大家重抢。 - 一次
notify_all:Condvar不把所有等待者都叫醒,而是叫醒至多一个、把其余的直接挪到 mutex 的等待队列上(requeue)。 - 全局停车场的一生:线程第一次停车时建表或扩表;表的桶数保持在线程数的 3 倍以上,只增不减,旧表永不释放。
- 横线:桶锁里能做什么、
ThreadData活多久、内存序:回调在桶锁里执行,不能 panic、不能再调停车场;叫醒方必须在持桶锁时先「锁住」对方的 parker;状态字上的Relaxed靠桶锁串行化成立。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> core[02 parking_lot_core] core --> mutex[03 Mutex 家族] core --> rwlock[04 RwLock] core --> cv[05 Condvar 与 Once] mutex --> api[06 lock_api] rwlock --> api cv --> api api --> v013[07 0.13 的变化] v013 --> design[08 设计取舍] design --> pit[09 常见坑] pit --> site[10 在本站中] site --> hands[11 动手]
实验环境
本机 Apple M2 Max(8 个性能核 + 4 个能效核,共 12 个逻辑核),macOS,rustc 1.99.1。实验程序在 exp-parking_lot/ 下,依赖锁定 parking_lot =0.12.5、parking_lot_core =0.9.12、lock_api =0.4.14,对照组有 std、crossbeam-utils 0.8.23 和 parking_lot 0.13.0。deadlock_detection feature 会改变被测对象(每次加锁多一次线程局部记账),所有计时实验都用不开这个 feature 的 crate。性能类一律 release、每组至少 3 次取中位数,写出测量时的负载;macOS 不能绑核。每个实验带一个反向验证。细节见 怎么读这一卷。
各部分
- 00 导读:版本、四条主线、阅读路线、实验方法;三个 crate 的分层、每个文件管什么、feature 表。
- 01 全景:停车场解决什么问题、和 std 怎么分工;一次有竞争的加锁、一次
notify_all,各走一遍。 - 02 parking_lot_core:本卷核心。哈希表与扩容、
WordLock、park 与四种 unpark、ThreadParker的平台实现、SpinWait、eventual fairness、死锁检测。 - 03 Mutex 家族:
RawMutex的一字节状态机、公平解锁与bump、FairMutex、ReentrantMutex、和 stdMutex比。 - 04 RwLock:状态字与两条等待队列、写者优先、可升级读、和 std
RwLock比。 - 05 Condvar 与 Once:requeue、一个 condvar 只能配一把 mutex;1 字节的
Once与毒化。 - 06 lock_api:raw trait 的契约、外壳与守卫、
GuardSend与send_guard、给自己的锁套外壳。 - 07 0.13 的变化:先放锁再叫醒、每次加锁的守卫状态、去掉的与修正的。
- 08 设计取舍:各章取舍汇总。
- 09 常见坑:用报错原文或现象做标题。
- 10 在本站中:本站经 tokio、dioxus、sqlx、pingora 间接依赖的 parking_lot。
- 11 动手:最小停车场、用 parking_lot_core 写信号量、用 lock_api 包自旋锁。
从「导读」开始。只想选一把锁的读者,读 01、03 和 std Mutex 比、09;想读懂停车场的,读 02;想写自己的锁的,读 06、02 和 11。