《同名不同魂:一文吃透 Python 的 Queue 与 asyncio.Queue(从入门到工程化最佳实践)》
面向对象:既在学并发基础的初学者,也在生产环境里追求稳定与吞吐的资深开发者
目标:讲清两者的设计哲学、使用边界与工程实践,配齐可复制的代码模板与踩坑清单
开篇引入:为什么这两个“队列”老被搞混?
Python 自 1991 年问世,以简洁语法和丰富生态成为 Web、数据工程与 AI 的“胶水语言”。在并发世界里,“队列”是最常用的解耦工具。但很多同学第一次接触并发时,会把 queue.Queue(下文简称 Queue)与 asyncio.Queue(下文简称 AQueue)当作“同物两名”。结果不是阻塞住事件循环,就是在多线程里糟糕地混用协程原语。
这篇文章从语言精要 → 高级实践 → 工程化落地,系统讲清两者的差异、适用场景与最佳实践。读完你将能回答:
- 我该在阻塞 I/O 的线程世界用 Queue,还是在协程世界用 AQueue?
- 如何做背压、限流、超时与优雅关停?
- 如何在同一项目中混合使用线程/协程/进程而不踩坑?
1. 概念速写:同是队列,语义不同
| 维度 | queue.Queue(线程队列) |
asyncio.Queue(协程队列) |
|---|---|---|
| 运行模型 | 多线程/同步阻塞 | 单线程/事件循环/协作式调度 |
| API 风格 | put()/get() 阻塞;put_nowait()/get_nowait() 非阻塞 |
await put()/await get() 协程挂起;put_nowait()/get_nowait() 非阻塞 |
| 背压与容量 | maxsize + 线程阻塞 |
maxsize + await 等待,不阻塞线程 |
| 取消/超时 | get(timeout=...) 抛 Empty;取消靠线程间协议 |
asyncio.wait_for()/Task.cancel() 原生支持取消 |
| 线程安全 | 线程安全 | 线程安全对协程而言无意义;不是跨线程安全 |
| 使用场景 | 阻塞库(如 requests、磁盘 I/O)、生产者—消费者(线程) |
海量并发 I/O(aiohttp、asyncpg、WebSocket 等) |
| 常见坑 | 在 CPU 密集任务上开太多线程;无界队列内存膨胀 | 在协程里使用阻塞函数导致卡 loop;跨线程误用 AQueue |
一句话:
- Queue 服务于线程/同步世界,通过阻塞协调背压;
- AQueue 服务于协程/异步世界,通过await 挂起实现背压与高并发。
2. 基础部分:语法与行为对照
2.1 线程队列最小可用实现(I/O 密集)
import queue, threading, time, random
def producer(q: queue.Queue, n=1000):
for i in range(n):
q.put(i) # 满了就阻塞,形成背压
q.put(None) # 哨兵,通知消费者结束
def consumer(q: queue.Queue, results: list):
while True:
x = q.get() # 空则阻塞等待
if x is None:
q.put(None) # 传递哨兵给其他消费者
q.task_done()
break
time.sleep(random.uniform(0.005, 0.01)) # 模拟 I/O
results.append(x * 2)
q.task_done()
def main():
q = queue.Queue(maxsize=1000)
results = []
ts = [threading.Thread(target=consumer, args=(q, results)) for _ in range(8)]
for t in ts: t.start()
producer(q, 5000)
q.join() # 等所有任务 task_done
for t in ts: t.join()
print(len(results))
if __name__ == "__main__":
main()
2.2 异步队列最小可用实现(海量连接)
import asyncio, random, aiohttp
async def producer(q: asyncio.Queue, urls):
for u in urls:
await q.put(u) # 满了就 await,事件循环可继续跑其他任务
await q.put(None) # 哨兵
async def fetch(session, url):
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as r:
return url, r.status
async def consumer(q: asyncio.Queue, results: list, sem: asyncio.Semaphore):
async with aiohttp.ClientSession() as s:
while True:
u

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



