asyncio.Queue更适合协程场景,因其put/get为awaitable、不阻塞事件循环,而queue.Queue是线程安全的同步阻塞队列,混用会导致事件循环卡死;它支持背压、task_done/join协同退出,且qsize等不可用于条件判断。

asyncio.Queue 为什么比普通 queue.Queue 更适合协程场景
因为 queue.Queue 是线程安全的阻塞队列,所有 put() 和 get() 都会直接阻塞当前线程;而 asyncio.Queue 是为协程设计的——它的 put() 和 get() 返回 awaitable,不会阻塞事件循环,能真正实现高并发吞吐。
常见错误是混用:比如在 async def 函数里调用 queue.Queue.put(),表面不报错,实则悄悄把整个事件循环卡住——尤其在大量生产者时,CPU 占用低但吞吐骤降。
- 必须全程使用
asyncio.Queue,不能和threading或queue混用 - 初始化时建议显式传入
maxsize(如asyncio.Queue(maxsize=1000)),否则默认无界,内存可能被撑爆 -
get()在空时自动挂起协程,put()在满时也自动挂起——这是背压(backpressure)的关键机制
如何正确启动多个生产者和消费者并安全退出
核心难点不是“怎么启”,而是“怎么停”:不能靠 while True + break 硬退,否则可能丢数据或死锁。推荐用 asyncio.Event 控制生命周期,或用哨兵值(sentinel)通知结束。
示例中常用 None 作为哨兵:
立即学习“Python免费学习笔记(深入)”;
await queue.put(None) # 生产者发完数据后放一个 None
消费者收到 None 后 break,但要注意:多个消费者时,每个都要收到一份哨兵,否则有的协程永远等下去。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 生产者数量 ≠ 消费者数量,但哨兵总数必须等于消费者数(否则部分消费者卡死)
- 更健壮的做法是用
asyncio.Event+queue.join():调用queue.task_done()标记每项处理完成,最后await queue.join()等待全部完成 - 退出前务必调用
queue.join(),否则未完成的put()可能被静默丢弃
asyncio.Queue 的 size、qsize() 和 full()/empty() 为什么不能当判断依据
qsize() 返回的是“快照值”,协程调度下它几乎立刻过期;full() 和 empty() 同理——它们不提供原子性保证,也不能替代 await queue.put() 或 await queue.get() 的等待逻辑。
典型误用:
if not queue.full(): # ❌ 危险!下一毫秒就可能满了
await queue.put(item)这会导致 QueueFull 异常(如果设了 maxsize)或逻辑错乱。
- 永远用
await queue.put(item),让底层自动处理等待和限流 - 需要预估积压量?用
queue.qsize()仅作监控打点,别用于控制流 - 想非阻塞尝试?用
try/except asyncio.QueueFull或asyncio.QueueEmpty,但代价是额外异常开销,一般不推荐
性能瓶颈往往不在 Queue 本身,而在消费者 I/O 或 CPU 密集操作
asyncio.Queue 的吞吐能力极强(单核轻松数万 ops/sec),但如果你的消费者要调 HTTP 接口、解析 JSON、写磁盘,这些才是真正的瓶颈。Queue 只是缓冲区,不是加速器。
容易被忽略的点:
- 消费者协程里用了
time.sleep()或json.loads()这类同步阻塞调用?必须换成await asyncio.sleep()和ujson等异步友好库 - 数据库操作没用 async driver(如
aiosqlite、asyncpg)?会拖垮整个事件循环 - 生产者太快、消费者太慢 → Queue 积压暴涨 → 内存飙升 → OOM。这时要加限速(如
asyncio.Semaphore控制并发生产数),而不是调大maxsize
真正压测时,先 mock 消费者逻辑(比如只 await asyncio.sleep(0.001)),确认 Queue 层面无瓶颈,再逐步替换成真实业务代码。


















