Semaphore与TCPConnector搭配的作用及100K请求优化疑问
关于asyncio Semaphore与aiohttp并发控制的疑问解答
一、代码里的Semaphore到底起啥作用?
这段代码里的asyncio.BoundedSemaphore(value=100)是用来限制并发执行的——只有拿到信号量的fetch协程,才能进入session.get(url)的HTTP请求环节,最多同时有100个协程在处理请求相关操作。
二、你的判断对不对?
你提到的两点,一半准确,一半需要补充:
- 关于“Semaphore多余”:从实际并发HTTP连接数来看,确实和aiohttp默认
TCPConnector(限制100连接)的效果重叠,就算放开Semaphore,Connector也会卡住超出的连接请求。但某些场景下它并非多余,后面会具体说明。 - 关于内存占用:你完全正确——不管加不加Semaphore,只要一次性创建100K个
fetch协程并加入任务列表,这些协程对象都会占用内存,Semaphore只是让它们处于等待状态,不会减少内存消耗。
三、加这个Semaphore有额外优势吗?
有几个场景下它能发挥独特作用:
- 控制全流程并发,不止HTTP连接:如果你的
fetch函数里除了发请求,还有解析数据、写入文件等异步操作,Semaphore可以限制整个fetch流程的并发数,避免这些环节同时跑太多导致资源紧张。 - 降低事件循环调度开销:一次性创建大量协程(比如100K个),事件循环要频繁调度这些等待状态的协程,会额外消耗系统资源。用Semaphore配合队列控制活跃协程数量,能减轻循环的调度压力。
- 兼容性更强:如果后续切换到没有内置连接池限制的HTTP客户端,Semaphore可以直接保留,不用大幅修改代码逻辑。
四、处理100K个URL该怎么搞?
绝对不能直接按原代码执行,原因很明确:
- 一次性创建100K个协程会瞬间占用大量内存,很可能导致内存溢出程序崩溃。
- 事件循环调度大量等待状态的协程,会严重降低运行效率。
最优方案是用异步队列+固定数量的工作协程:
- 把所有URL提前放入一个异步队列中。
- 启动固定数量的工作协程(比如100个),每个协程循环从队列中取出URL进行处理,直到队列为空。
- 这种方式下,同时活跃的协程数就是你设置的工作协程数,内存占用可控,事件循环的调度压力也会小很多。
给你一个简单的示例代码:
import aiohttp import asyncio async def fetch(session, url, sema): async with sema, session.get(url) as response: return await response.text() async def worker(session, queue, sema): while not queue.empty(): url = await queue.get() try: html = await fetch(session, url, sema) print(html[:100]) finally: queue.task_done() async def main(): # 模拟100K个URL urls = ['http://python.org', 'https://google.com', 'http://yifei.me'] * 33334 queue = asyncio.Queue() for url in urls: await queue.put(url) sema = asyncio.BoundedSemaphore(value=100) async with aiohttp.ClientSession() as session: # 启动100个工作协程 tasks = [asyncio.create_task(worker(session, queue, sema)) for _ in range(100)] await queue.join() # 等待队列中所有任务处理完成 for task in tasks: task.cancel() if __name__ == '__main__': asyncio.run(main())
内容的提问来源于stack exchange,提问作者FunztNisch
相关产品推荐
相关产品推荐

