asyncio.Queue能解决协程间变量共享问题,因其是异步安全队列,基于asyncio.Event和锁实现并发协调;普通变量在协程中并发读写易致状态不一致,引发异常或数据丢失。

asyncio.Queue 为什么能解决协程间变量共享问题
因为 asyncio.Queue 是专为异步环境设计的线程安全+协程安全队列,底层用 asyncio.Event 和锁机制协调多个 await 操作。普通全局变量、列表或字典在 async 函数里被多个协程并发读写时,不会触发 GIL 保护(因为没进入 CPU 密集态),但会因调度中断导致状态不一致——比如一个协程刚 append 一半就被挂起,另一个协程读到残缺数据。
常见错误现象:IndexError、读到空值、数据重复或丢失、程序卡死在 queue.get() 不返回。
使用场景:生产者-消费者模型(如爬虫抓取 + 解析分离)、任务分发(如批量调用 API 后统一处理响应)、跨协程传递中间结果。
怎么正确初始化和使用 asyncio.Queue
必须在同一个事件循环内创建,不能在同步函数里初始化后传给协程;也不能在不同 loop 中混用(比如用 threading.Thread 启动新 loop 时直接传入主 loop 的 Queue)。
-
asyncio.Queue(maxsize=0)表示无限制,但实际受内存约束;设为正数可防止生产过快压垮内存 - 不要用
queue.put_nowait(item)在协程中——它不 await,可能抛asyncio.QueueFull,且破坏异步流控 - 务必搭配
await queue.put(item)和await queue.get(),并记得调用queue.task_done()配合await queue.join()做完成确认
示例:
import asyncio
<p>async def producer(queue):
for i in range(3):
await queue.put(f"item-{i}")
await asyncio.sleep(0.1)</p><p>async def consumer(queue):
while True:
try:
item = await queue.get()
print(f"got {item}")
queue.task_done() # 标记本次 get 完成
except asyncio.CancelledError:
break</p><p>async def main():
q = asyncio.Queue()
await asyncio.gather(
producer(q),
consumer(q),
return_exceptions=True
)
await q.join() # 等待所有已 put 的 item 都被 task_done</p>
asyncio.Queue 和普通 queue.Queue 的关键区别
queue.Queue 是同步阻塞的,用在多线程里靠 GIL 和内部锁;一旦放进 async 函数里调用 put() 或 get(),就会阻塞整个事件循环——表面看没报错,实际其他协程全卡住。
性能影响:用错会把异步程序变成“伪异步”,吞吐量掉到同步水平;兼容性上,asyncio.Queue 不支持 qsize() 实时准确返回(文档明确说“not reliable”),也不提供 empty()/full() 的强语义判断。
参数差异:asyncio.Queue 没有 block 或 timeout 参数——它的阻塞/超时必须靠 asyncio.wait_for() 包裹 await queue.get() 来实现。
容易踩的坑:忘记 task_done、join 卡死、异常中断未清理
最常被忽略的是 task_done() 调用时机:只在成功处理完一个 item 后调用一次,不能漏,也不能多调;否则 queue.join() 永远等不到清零,协程无法退出。
另一个隐形陷阱是消费者协程因异常退出(比如解析 JSON 失败 raise 了 Exception),没执行 queue.task_done(),导致后续 join() 死等。
- 建议在
try/finally中调用task_done(),确保无论是否异常都标记完成 - 如果消费者需要主动退出(比如收到 None 哨兵值),要在退出前检查是否还有未完成任务,必要时补调
task_done() - 不要在
queue.get()外层套while True:却没设退出条件——容易变成孤儿协程,拖慢整个程序关闭
复杂点在于:多个消费者共用一个队列时,task_done() 是按每个 get() 计数的,不是按生产者 put 次数;所以哪怕只有一个消费者,只要 get() 了 N 次,就必须 task_done() N 次,才能让 join() 返回。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











