aiohttp性能问题:主线程执行连接导致请求速度过慢
分析你的异步代码性能问题
嘿,我来帮你拆解下这个问题!你提到异步实现比线程池版本慢,且URL越多耗时越长,还特意用不存在的服务器触发超时——这个测试场景太关键了,连接超时最容易暴露异步代码里的隐性阻塞点,咱们一步步捋:
最可能的核心问题:连接建立是同步阻塞的
很多人写异步代码时,容易踩一个坑:看起来用了async/await,但底层连接建立的逻辑还是同步的!比如:
- 用了完全不支持异步的同步网络库(比如普通的
requests); - 手动写socket时用了
socket.connect()这种同步方法,而非异步的socket API(比如asyncio.open_connection()); - 异步库默认用系统同步DNS解析,在超时场景下会串行等待每个域名解析完成。
这种情况下,你的“异步”代码本质是串行阻塞执行:每个URL都要等完超时时间(比如30秒)才会处理下一个,50个URL就是50×30秒的叠加耗时,自然比线程池的并行超时(总耗时接近单个超时时间)慢得多。
其次要检查:任务是否真的被并发调度了
就算用了正确的异步库,如果任务调度方式错了,也会变成串行执行。比如你可能写了这样的代码:
# ❌ 错误:逐个await,串行执行 for url in urls: await fetch_url(url)
而正确的并发写法应该是把所有任务放进asyncio.gather(),让事件循环同时调度:
# ✅ 正确:并发执行所有任务 tasks = [fetch_url(url) for url in urls] await asyncio.gather(*tasks)
前者每个任务必须等上一个完成才启动,超时时间叠加;后者所有任务同时触发,总耗时只受最长的那个超时影响。
其他可能的坑
- 异步库使用姿势错误:比如用
aiohttp时每次请求都新建ClientSession,没有复用连接池;或者没设置正确的超时参数,导致底层逻辑异常阻塞。 - 事件循环被其他同步操作阻塞:如果你的代码里还有CPU密集型计算、调用同步第三方库的逻辑,会占满事件循环的时间片,导致异步连接任务无法及时被调度,看起来就像连接没异步一样。
给你一个修正后的示例(用aiohttp)
import asyncio import aiohttp async def fetch(session, url): try: # 设置总超时,避免无限等待 async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp: return f"{url} 响应:{resp.status}" except Exception as e: return f"{url} 错误:{str(e)}" async def main(urls): # 复用ClientSession,避免重复创建连接开销 async with aiohttp.ClientSession() as session: # 创建所有任务,并发执行 tasks = [fetch(session, url) for url in urls] results = await asyncio.gather(*tasks) for res in results: print(res) if __name__ == "__main__": # 模拟50个不存在的URL urls = [f"http://nonexistent-server-{i}.com" for i in range(50)] asyncio.run(main(urls))
这个版本里,连接建立、DNS解析(如果额外配置aiodns会更优)都是异步的,且所有任务并发调度,总耗时会接近5秒(设置的超时时间),而不是50×5秒的叠加耗时。
内容的提问来源于stack exchange,提问作者Corneliu Maftuleac




