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。
💬 关联讨论
发起讨论加载中...