Condvar 与 Once
这一部分的两个原语都很小:Condvar 一个 AtomicPtr(8 字节),Once 一个 AtomicU8(1 字节)。它们不另起炉灶,Once 的慢路径照搬 RawMutex::lock_slow,Condvar 则直接改 RawMutex 的状态字节,把自己的等待者整体挪到 mutex 的停车队列里。
flowchart LR
subgraph cv["Condvar:state = 配对 mutex 的地址"]
cq["停车场队列 key = condvar"]
end
subgraph mx["parking_lot::Mutex"]
mq["停车场队列 key = mutex"]
st["RawMutex 状态:LOCKED / PARKED"]
end
cq -->|"notify_all:unpark_requeue,改 key 后接到队尾"| mq
cv -.->|"桶锁里 mark_parked_if_locked / mark_parked"| st
subgraph on["Once:DONE / POISON / LOCKED / PARKED"]
oq["停车场队列 key = once"]
end
oq -->|"执行者结束:unpark_all"| done["全部醒来,读状态"]
两章:
- Condvar:主线 2 全部。
wait先在桶锁里登记 mutex、入队,再在before_sleep里放锁;notify_all在 mutex 被锁时一个都不叫醒,全部挪到 mutex 队列;返回值是受影响的线程数;notify_one也可能只挪不叫;只接受parking_lot::MutexGuard;两把 mutex 的 panic;wait_while的超时分支;惊群实测(E6)与两把 mutex 实测(E15)。 - Once:四个位;快路径一次
Acquire读;慢路径与RawMutex同骨架;PanicGuard毒化并叫醒所有等待者;与 stdOnce、OnceLock、LazyLock的大小、速度与毒化语义(E10)。
全景时序图在 一次 notify_all;unpark_requeue 的五种 RequeueOp 和 lock_bucket_pair 在 park 与四种 unpark。
和 std 的差别一览
| parking_lot | std(本机 macOS) | |
|---|---|---|
Condvar 大小 | 8 | 16(pthread,懒分配);Linux 上 4 |
notify_all | requeue:最多叫醒一个,其余挪到 mutex 队列 | 叫醒全部 |
| 虚假唤醒 | 文档承诺没有 | 文档说可能有 |
| 配对的 mutex | 只能是 parking_lot::Mutex;同一时刻一把,没有等待者后可换 | 任意 std Mutex;本机 pthread 版一旦用过一把就不能换 |
Once 大小 | 1 | 8;Linux 上 4 |
| 初始化 panic | 毒化,call_once_force 可重试 | Once 毒化;OnceLock 不毒化;LazyLock 毒化 |
本部分的实验
| # | 目录 | 看什么 | 章 |
|---|---|---|---|
| E6 | exp-parking_lot/condvar-herd/ | N = 1 / 4 / 16 / 64 个等待者,持锁与放锁两种 notify_all,返回值、总时间、首个拿到锁的时间、交接间隔、上下文切换次数;循环 notify_one 作中间对照 | Condvar |
| E15 | exp-parking_lot/condvar-two-mutexes/ | 两把 mutex 配一个 condvar:同时与先后两种用法,parking_lot 与 std 的 panic 原文 | Condvar |
| — | exp-parking_lot/mapped-guard-condvar/ | MappedMutexGuard、FairMutex 守卫传给 wait 的编译错误原文 | Condvar |
| E10 | exp-parking_lot/once/ | 快路径 ns/次;12 线程并发只执行一次;毒化与 call_once_force;等待中毒化 | Once |
0.13 把 Condvar 移进了 lock_api(泛型 Condvar + RawCondvar),Once 增加了 new_completed、is_completed、wait 等方法;本部分只写 0.12.5,变化见 0.13 的变化。
💬 关联讨论
发起讨论加载中...