Rust in Everything
基于 loom 0.7.2

Condvar 用 if 等待,loom 也通过了

主线位置:主线 4「看到什么、看不到什么」在条件变量上的两条:loom 的 Condvar 不虚假唤醒(假阴性),wait_timeout 不超时(假阳性)。机制见 Condvar、Notify 与 park。

flowchart LR
  subgraph cv["loom::sync::Condvar"]
    w1["wait:只在 notify 后醒"] --> p1["if 与 while 都通过(8 个交错)"]
    w2["wait_timeout:时长被忽略"] --> p2["没人 notify 时报 deadlock,超时分支测不到"]
  end
  subgraph nt["loom::sync::Notify"]
    w3["wait:每个对象一轮最多伪唤醒一次"] --> p3["if 写法第 5 个交错断言失败"]
  end

现象

代码审查时有人指出「Condvar::wait 要放在 while 里」,回答是「loom 测过了」。loom 确实通过,但它证明不了 if 写法是对的:真实的 Condvar 可能虚假唤醒,醒来时条件还没满足。

最小复现

E9(Condvar、Notify 与 park):一个线程持锁置 ready = true 再 notify_one;主线程持锁等,醒来断言 ready。

写法结果
Condvar,if !*g { g = cv.wait(g) }8 个交错,全部通过
Condvar,while !*g { g = cv.wait(g) }8 个交错,全部通过
Notify + AtomicBool,if !ready.load(Acquire) { n.wait() }(反向验证)第 5 个交错断言失败「woke without ready」
Condvar::wait_timeout(1 ms),没有任何线程通知,断言 timed_out()第 1 个交错报「deadlock; threads = [(Id(0), Blocked(Location(None)))]」,随后 abort

前两行交错数一样,因为 loom 里 while 的第二次判断永远为假。第三行说明模型形状没问题:换一个会伪唤醒的原语,if 就被抓到。

源码出处

  • Condvar::wait 把线程挂到等待队列、放锁、park,只有 notify_one / notify_all 才把它移出来,没有产生伪唤醒的分支(rt/condvar.rs:39-60)。
  • wait_timeout 忽略时长,就是一次 wait,timed_out() 恒为 false(sync/condvar.rs:44-53)。
  • Notify 是 loom 独有的原语,每个对象一轮里最多走一次 Spurious 分支(Path 的第三种分支)。

怎么改

  • 代码审查照 std 的规矩来:Condvar::wait 一律放在 while 里,或者用 wait_while。这一条 loom 帮不上。
  • 要让 loom 检查「容忍虚假唤醒」:被测代码的等待原语在 loom 下换成 loom::sync::Notify(只能一个线程等),或者在切换层里自己包一个偶尔提前返回的 wait。
  • 超时分支:不放进模型。依赖超时兜底的代码,在 loom 下会被报成死锁;这种报错先看是不是 wait_timeout 没人通知。

小结

  • loom 的 Condvar 不虚假唤醒:if 与 while 都是 8 个交错通过(E9),这是假阴性。
  • 同一个形状换成会伪唤醒一次的 Notify,if 第 5 个交错被抓到。
  • wait_timeout 永不超时:没人通知时第 1 个交错报死锁,超时分支测不到,这是假阳性。
  • 等待一律 while;需要 loom 检查时换成 Notify。

💬 关联讨论

发起讨论
加载中...