1. 为什么需要asyncio队列与生产者消费者模式
在Python的异步编程中,我们经常会遇到这样的场景:一组任务(生产者)不断生成数据,另一组任务(消费者)需要处理这些数据。如果生产者和消费者的处理速度不一致,就会导致资源浪费或系统阻塞。这就是asyncio队列和生产者消费者模式要解决的核心问题。
我最近在一个爬虫项目中就遇到了这种情况。爬取页面(生产者)的速度远快于解析页面(消费者)的速度,导致内存中堆积了大量未处理的HTML内容。通过引入asyncio.Queue,不仅解决了内存暴涨的问题,还实现了优雅的流量控制。
注意:asyncio队列与线程安全队列(如queue.Queue)的关键区别在于,它是专为协程设计的,不会阻塞事件循环。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. asyncio队列类型详解
2.1 Queue:基础FIFO队列
这是最常用的队列类型,遵循先进先出原则。它的核心方法包括:
- put(item): 放入项目
- get(): 获取项目
- join(): 等待所有项目被处理
- task_done(): 标记项目处理完成
python复制import asyncio
async def producer(queue):
for i in range(5):
await queue.put(i)
print(f'Produced {i}')
async def consumer(queue):
while True:
item = await queue.get()
print(f'Consumed {item}')
queue.task_done()
async def main():
queue = asyncio.Queue(maxsize=3) # 限制队列大小防止内存溢出
producers = [asyncio.create_task(producer(queue)) for _ in range(2)]
consumers = [asyncio.create_task(consumer(queue)) for _ in range(3)]
await asyncio.gather(*producers)
await queue.join() # 等待所有项目处理完成
for c in consumers:
c.cancel()
asyncio.run(main())
2.2 PriorityQueue:优先级队列
当需要按优先级处理任务时使用。放入队列的项目必须是可比较的元组(优先级, 数据):
python复制priority_queue = asyncio.PriorityQueue()
await priority_queue.put((2, '次要任务'))
await priority_queue.put((1, '紧急任务'))
2.3 LifoQueue:后进先出队列
行为类似于栈,最后放入的项目最先被取出:
python复制lifo_queue = asyncio.LifoQueue()
await lifo_queue.put(1)
await lifo_queue.put(2)
print(await lifo_queue.get()) # 输出2
3. 生产者消费者模式的实战实现
3.1 基础实现模板
python复制async def producer(queue, id):
while True:
data = await fetch_data() # 模拟获取数据
await queue.put(data)
print(f'Producer {id} produced {data}')
async def consumer(queue, id):
while True:
data = await queue.get()
try:
await process_data(data) # 模拟处理数据
finally:
queue.task_done()
print(f'Consumer {id} processed {data}')
async def main():
queue = asyncio.Queue(maxsize=10)
producers = [asyncio.create_task(producer(queue, i)) for i in range(3)]
consumers = [asyncio.create_task(consumer(queue, i)) for i in range(5)]
await asyncio.sleep(10) # 运行10秒
for p in producers:
p.cancel()
await queue.join() # 等待剩余任务完成
for c in consumers:
c.cancel()
3.2 动态调节生产者速度
在实际项目中,我经常使用队列大小来动态调节生产速度:
python复制async def smart_producer(queue):
while True:
if queue.qsize() > queue.maxsize * 0.8: # 队列接近满时
await asyncio.sleep(1) # 减慢生产速度
else:
await queue.put(await fetch_data())
3.3 批量消费模式
对于可以批量处理的任务,可以显著提高效率:
python复制async def batch_consumer(queue):
batch = []
while True:
try:
item = await asyncio.wait_for(queue.get(), timeout=0.5)
batch.append(item)
if len(batch) >= 50: # 达到批量大小
await process_batch(batch)
batch = []
except asyncio.TimeoutError:
if batch: # 超时但batch不为空
await process_batch(batch)
batch = []
4. 高级应用场景与性能优化
4.1 限制并发消费者数量
通过信号量控制同时工作的消费者数量:
python复制async def controlled_consumer(queue, semaphore):
while True:
async with semaphore:
item = await queue.get()
try:
await process_item(item)
finally:
queue.task_done()
async def main():
queue = asyncio.Queue()
semaphore = asyncio.Semaphore(10) # 最多10个并发消费者
consumers = [asyncio.create_task(controlled_consumer(queue, semaphore))
for _ in range(20)] # 创建20个消费者任务
4.2 多队列路由模式
根据任务类型分发到不同队列:
python复制async def router(queues):
while True:
task = await get_task() # 获取任务
if task.type == 'A':
await queues['A'].put(task)
elif task.type == 'B':
await queues['B'].put(task)
async def typed_consumer(queue, type):
while True:
task = await queue.get()
print(f'Processing {type} task')
queue.task_done()
async def main():
queues = {
'A': asyncio.Queue(),
'B': asyncio.Queue()
}
asyncio.create_task(router(queues))
asyncio.create_task(typed_consumer(queues['A'], 'A'))
asyncio.create_task(typed_consumer(queues['B'], 'B'))
4.3 队列监控与统计
在生产环境中,监控队列状态至关重要:
python复制async def monitor(queue, interval=5):
while True:
print(f'Queue size: {queue.qsize()}')
print(f'Queue maxsize: {queue.maxsize}')
print(f'Pending tasks: {queue._unfinished_tasks}') # 注意:访问保护成员
await asyncio.sleep(interval)
5. 常见问题与调试技巧
5.1 死锁预防
我曾在项目中遇到这样的情况:消费者在处理过程中抛出异常,导致task_done()未被调用,join()永远阻塞。解决方案是:
python复制async def safe_consumer(queue):
while True:
item = await queue.get()
try:
await process(item)
except Exception as e:
print(f'Error processing {item}: {e}')
finally:
queue.task_done() # 确保总是调用
5.2 性能瓶颈定位
使用asyncio的调试工具:
python复制import logging
logging.basicConfig(level=logging.DEBUG)
asyncio.run(main(), debug=True)
这会显示队列操作耗时、任务切换等信息。
5.3 优雅关闭模式
实现可控制的关闭流程:
python复制async def shutdown(signal, queue, tasks):
print('Shutting down...')
for task in tasks:
task.cancel()
await queue.join() # 等待剩余任务完成
asyncio.get_event_loop().stop()
async def main():
queue = asyncio.Queue()
tasks = [asyncio.create_task(consumer(queue)) for _ in range(5)]
loop = asyncio.get_event_loop()
for sig in (signal.SIGTERM, signal.SIGINT):
loop.add_signal_handler(
sig, lambda: asyncio.create_task(shutdown(sig, queue, tasks)))
6. 与其他技术的对比与整合
6.1 对比多线程队列
| 特性 | asyncio.Queue | queue.Queue |
|---|---|---|
| 设计目标 | 协程间通信 | 线程间通信 |
| 阻塞行为 | 挂起协程 | 阻塞线程 |
| 性能 | 高(无锁) | 中(需要锁) |
| 内存占用 | 低 | 中 |
| 适用场景 | I/O密集型 | CPU密集型 |
6.2 与Redis队列集成
对于分布式场景,可以结合Redis:
python复制async def redis_to_asyncio_bridge(redis_queue, asyncio_queue):
while True:
item = await redis_queue.get() # 使用aioredis
await asyncio_queue.put(item)
async def hybrid_consumer(asyncio_queue):
while True:
item = await asyncio_queue.get()
await process_item(item)
asyncio_queue.task_done()
6.3 与数据库操作结合
在Web应用中处理数据库写入:
python复制async def db_writer(queue):
pool = await create_db_pool()
while True:
records = await queue.get()
async with pool.acquire() as conn:
async with conn.transaction():
for record in records:
await conn.execute(INSERT_SQL, record)
queue.task_done()
我在实际项目中发现,将队列大小设置为数据库连接池大小的2-3倍,可以获得最佳吞吐量。
