Python asyncio 并发请求里最容易踩的 4 个坑

举报
yd_247334186 发表于 2026/11/02 16:12:24 2026/11/02
【摘要】 最近在写一个批量调用接口的小脚本,从 requests 换到 asyncio + aiohttp 之后速度确实上去了,但也连着踩了几个坑,记录一下。在协程里调用了阻塞函数time.sleep()、requests.get()、普通的文件读写都会卡住整个事件循环,其他协程全部停下来。等待用 await asyncio.sleep()HTTP 用 aiohttp 或 httpx 的异步客户端实在...

最近在写一个批量调用接口的小脚本,从 requests 换到 asyncio + aiohttp 之后速度确实上去了,但也连着踩了几个坑,记录一下。

  1. 在协程里调用了阻塞函数
    time.sleep()、requests.get()、普通的文件读写都会卡住整个事件循环,其他协程全部停下来。
  • 等待用 await asyncio.sleep()
  • HTTP 用 aiohttp 或 httpx 的异步客户端
  • 实在绕不开的阻塞调用,用 await asyncio.to_thread(func, *args) 丢到线程里
  1. 一次性 gather 几千个任务
    asyncio.gather(*[fetch(u) for u in urls]) 写起来很爽,但几千个请求同时发出去,要么被对方限流,要么本地连接数打满报错。加一个信号量控制并发:
sem = asyncio.Semaphore(20)

async def fetch(session, url):
    async with sem:
        async with session.get(url) as resp:
            return await resp.text()
  1. 每个请求都新建 ClientSession
    ClientSession 内部有连接池,每次新建等于放弃复用连接,还会出现 Unclosed client session 的警告。整个程序共用一个就够了:
async with aiohttp.ClientSession() as session:
    results = await asyncio.gather(*(fetch(session, u) for u in urls))
  1. 一个任务报错,结果全丢
    gather 默认遇到第一个异常就抛出,其他已经成功的结果拿不到。加上 return_exceptions=True,再逐个判断:
results = await asyncio.gather(*tasks, return_exceptions=True)
for url, r in zip(urls, results):
    if isinstance(r, Exception):
        print(f"{url} 失败: {r}")

Python 3.11 以后也可以用 asyncio.TaskGroup,写法更清晰,但它的策略是一个失败就取消其余任务,适合"要么全成功要么全失败"的场景,按需选择。

小结:异步不是加个 async 就变快,关键是别阻塞事件循环、控制并发、复用连接、处理好异常。希望对刚上手 asyncio 的同学有帮助。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。