
本文解释了 asyncio.condition 在多任务协作中因通知时机不当导致死锁的根本原因,并通过对比代码演示如何确保等待任务先就绪、再通知,从而避免死锁。
本文解释了 asyncio.condition 在多任务协作中因通知时机不当导致死锁的根本原因,并通过对比代码演示如何确保等待任务先就绪、再通知,从而避免死锁。
在使用 asyncio.Condition 实现协程间同步时,一个常见却容易被忽视的陷阱是:notify_all()(或 notify())必须在至少一个任务已进入 wait() 状态后调用,否则通知将“石沉大海”——无人接收,后续等待者永远阻塞,最终引发逻辑死锁。
你提供的代码中,main() 函数先创建并启动 setter 任务,再创建三个 monitor 任务:
async def main():
cond = asyncio.Condition()
async with asyncio.TaskGroup() as tg:
tg.create_task(setter(cond)) # ✅ 先启动 setter
[tg.create_task(monitor(cond)) for _ in range(3)] # ❌ 后启动 monitor(此时可能尚未 await condition.wait())由于 setter 执行迅速(仅 sleep(1) 后即 notify_all()),而 monitor 任务虽已调度,但其 async with condition: 和 await condition.wait() 尚未执行完成——它们甚至还没来得及注册到条件变量的等待队列中。结果就是:notify_all() 发出时,等待队列为空,通知被直接丢弃;随后各 monitor 任务陆续执行到 await condition.wait(),便无限期挂起,形成死锁。
✅ 正确做法是确保所有等待方已就位,再发出通知。最稳妥的方式是显式控制启动顺序,例如:
立即学习“Python免费学习笔记(深入)”;
async def main():
cond = asyncio.Condition()
async with asyncio.TaskGroup() as tg:
# 先批量启动 monitor,让它们进入 wait 状态
monitors = [tg.create_task(monitor(cond), name=f"monitor-{i}") for i in range(3)]
# 等待所有 monitor 进入 wait(可选:加轻量级同步信号,如 asyncio.Event)
await asyncio.sleep(0.01) # 简单让协程调度器推进一步(仅用于演示)
# 再启动 setter
tg.create_task(setter(cond))更健壮的工业级写法是引入 asyncio.Event 作为“就绪信号”:
async def monitor(condition: asyncio.Condition, ready_event: asyncio.Event):
current_task_name = asyncio.current_task().get_name()
print(f'Current task {current_task_name} started')
async with condition:
ready_event.set() # 告知:我已进入 wait 前的临界区
await condition.wait()
print(f'Condition lock released for task {current_task_name}, start doing smthg')
await asyncio.sleep(2)
print(f'Current task {current_task_name} finished')
async def setter(condition: asyncio.Condition, ready_event: asyncio.Event):
print(f'Setter starts')
# 等待所有 monitor 就绪
await ready_event.wait()
async with condition:
await asyncio.sleep(1)
condition.notify_all()
print('Setter finished')
print('Setter unlocked')
async def main():
cond = asyncio.Condition()
ready_event = asyncio.Event()
async with asyncio.TaskGroup() as tg:
# 启动 monitor(它们会自行 set ready_event)
for i in range(3):
tg.create_task(monitor(cond, ready_event), name=f"monitor-{i}")
# 启动 setter(它会等待 ready_event)
tg.create_task(setter(cond, ready_event))? 关键总结:
-
asyncio.Condition.wait()是可中断的挂起点,但notify_all()不会“缓存”——它只唤醒当前正在等待的协程; - 条件变量不是消息队列,没有通知积压机制;
- 多任务场景下,务必保证“等待 → 通知”时序,推荐用
asyncio.Event或显式延迟(谨慎使用)协调启动顺序; - 调试时可添加日志(如
print("About to wait...")/"Notified N tasks")辅助验证等待队列状态。
遵循这一原则,即可安全地利用 asyncio.Condition 构建高效的异步生产者-消费者、屏障(barrier)或状态同步逻辑。


















