Rust in Everything
基于 parking_lot 0.12.5

parking_lot 源码解析

全卷对着一个 tag 写:

crate版本源码链接用的 tag
parking_lot0.12.5parking_lot-v0.12.5(提交 d7828ff,「chore: release」,2025-09-30)
parking_lot_core0.9.12同上;tag parking_lot_core-v0.9.12 指向同一个提交
lock_api0.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 不能绑核。每个实验带一个反向验证。细节见 怎么读这一卷。

各部分

  1. 00 导读:版本、四条主线、阅读路线、实验方法;三个 crate 的分层、每个文件管什么、feature 表。
  2. 01 全景:停车场解决什么问题、和 std 怎么分工;一次有竞争的加锁、一次 notify_all,各走一遍。
  3. 02 parking_lot_core:本卷核心。哈希表与扩容、WordLock、park 与四种 unpark、ThreadParker 的平台实现、SpinWait、eventual fairness、死锁检测。
  4. 03 Mutex 家族:RawMutex 的一字节状态机、公平解锁与 bump、FairMutex、ReentrantMutex、和 std Mutex 比。
  5. 04 RwLock:状态字与两条等待队列、写者优先、可升级读、和 std RwLock 比。
  6. 05 Condvar 与 Once:requeue、一个 condvar 只能配一把 mutex;1 字节的 Once 与毒化。
  7. 06 lock_api:raw trait 的契约、外壳与守卫、GuardSend 与 send_guard、给自己的锁套外壳。
  8. 07 0.13 的变化:先放锁再叫醒、每次加锁的守卫状态、去掉的与修正的。
  9. 08 设计取舍:各章取舍汇总。
  10. 09 常见坑:用报错原文或现象做标题。
  11. 10 在本站中:本站经 tokio、dioxus、sqlx、pingora 间接依赖的 parking_lot。
  12. 11 动手:最小停车场、用 parking_lot_core 写信号量、用 lock_api 包自旋锁。

从「导读」开始。只想选一把锁的读者,读 01、03 和 std Mutex 比、09;想读懂停车场的,读 02;想写自己的锁的,读 06、02 和 11。

💬 关联讨论

发起讨论
加载中...