Python Asyncio Condition 死锁问题:为何会出现死锁?
Python Asyncio Condition 死锁问题:为何会出现死锁?
兄弟,别慌!作为asyncio新手碰到这种问题太正常了,我给你掰碎了说,保证听得懂~
首先得先搞懂asyncio.Condition的两个核心逻辑(用大白话讲):
- 当你用
async with condition:的时候,任务会先去抢一把“内部小锁”,抢到了才能进代码块干活,没抢到就只能原地等别人把锁放了; await condition.wait()是个“蹲通知”的操作:它会先把手里的小锁放了,然后蹲在那儿等有人喊它(也就是notify_all()/notify());等听到喊它的声音后,它会再去抢小锁,抢到了才能继续往下走。而且要注意:通知是“过期不候”的——你要是在蹲之前人家就喊过了,那你根本听不到,会一直蹲下去!
现在咱们一步步扒你当前代码的问题:
你现在是先创建setter任务,再创建3个monitor任务,流程是这样的:
- 事件循环先跑
setter,它一上来就抢到了Condition的内部小锁(这时候还没其他任务跟它抢); setter睡1秒,这时候事件循环会去启动那3个monitor任务,但这3个任务一上来就想进async with condition:,结果小锁还被setter攥着呢,只能原地等;- 1秒到了,
setter调用notify_all()——但这时候3个monitor还在蹲小锁呢,根本没开始执行wait(),这个通知直接就“白喊了”,没人接; setter退出async with,把小锁放了,打印出“Setter unlocked”;- 这时候
monitor任务终于能抢到小锁,进入代码块,然后调用await condition.wait()——但这时候之前的通知早就发过了,之后再也没人喊它们了!所以这三个任务会一直蹲在那儿等,永远不会结束。而你的TaskGroup要等所有任务都完成才会结束,自然就卡死(也就是你说的死锁)了。
那为啥换个顺序就正常了?
如果你先创建monitor任务,再创建setter,流程就变成:
- 3个
monitor任务先启动,它们挨个抢小锁,抢到的那个进async with后,马上调用wait()——这时候它会把小锁放了,然后蹲通知; - 第二个
monitor接着抢到小锁,同样调用wait()放锁蹲通知;第三个也是一样; - 然后
setter启动,抢到小锁,睡1秒后喊notify_all()——这时候3个monitor都在蹲通知,一听到喊声就会重新抢小锁,然后继续干活,最后都能正常结束,自然就没毛病了。
给你新手友好的解决办法:
最简单的就是保持“先创建需要等通知的任务(也就是你的monitor),再创建发通知的任务(setter)”的顺序,就像你试的那样,完全能解决问题。如果以后有特殊情况必须先启动发通知的任务,那得想办法让它等一等,等所有等待任务都蹲好再发通知,但那是进阶内容,你现在先把这个基础逻辑吃透就够啦!
备注:内容来源于stack exchange,提问作者IzaeDA




