Rust in Everything
基于 parking_lot 0.12.5

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["全部醒来,读状态"]

两章:

  1. Condvar:主线 2 全部。wait 先在桶锁里登记 mutex、入队,再在 before_sleep 里放锁;notify_all 在 mutex 被锁时一个都不叫醒,全部挪到 mutex 队列;返回值是受影响的线程数;notify_one 也可能只挪不叫;只接受 parking_lot::MutexGuard;两把 mutex 的 panic;wait_while 的超时分支;惊群实测(E6)与两把 mutex 实测(E15)。
  2. Once:四个位;快路径一次 Acquire 读;慢路径与 RawMutex 同骨架;PanicGuard 毒化并叫醒所有等待者;与 std Once、OnceLock、LazyLock 的大小、速度与毒化语义(E10)。

全景时序图在 一次 notify_all;unpark_requeue 的五种 RequeueOp 和 lock_bucket_pair 在 park 与四种 unpark。

和 std 的差别一览

parking_lotstd(本机 macOS)
Condvar 大小816(pthread,懒分配);Linux 上 4
notify_allrequeue:最多叫醒一个,其余挪到 mutex 队列叫醒全部
虚假唤醒文档承诺没有文档说可能有
配对的 mutex只能是 parking_lot::Mutex;同一时刻一把,没有等待者后可换任意 std Mutex;本机 pthread 版一旦用过一把就不能换
Once 大小18;Linux 上 4
初始化 panic毒化,call_once_force 可重试Once 毒化;OnceLock 不毒化;LazyLock 毒化

本部分的实验

#目录看什么章
E6exp-parking_lot/condvar-herd/N = 1 / 4 / 16 / 64 个等待者,持锁与放锁两种 notify_all,返回值、总时间、首个拿到锁的时间、交接间隔、上下文切换次数;循环 notify_one 作中间对照Condvar
E15exp-parking_lot/condvar-two-mutexes/两把 mutex 配一个 condvar:同时与先后两种用法,parking_lot 与 std 的 panic 原文Condvar
—exp-parking_lot/mapped-guard-condvar/MappedMutexGuard、FairMutex 守卫传给 wait 的编译错误原文Condvar
E10exp-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 的变化。

💬 关联讨论

发起讨论
加载中...