生成一个移动端标题,只需要一个标题,不需要其他的任何文本。

Python 异步编程实战:用 asyncio 把接口耗时从 30 秒压到 3 秒

一、问题场景

假设要批量拉取 30 个外部接口的数据做聚合,同步写法如下:

python 复制代码
import requests, time

def fetch(url):
    return requests.get(url, timeout=5).json()

start = time.time()
data = [fetch(u) for u in URLS]   # 30 个串行请求
print(time.time() - start)        # 约 30s

这类任务的瓶颈在 I/O 等待,CPU 基本空转。线程池能缓解,但线程切换与内存开销随并发数线性增长;asyncio 用单线程协程在等待期间切换任务,更适合高并发 I/O 场景。

二、核心机制

  • 协程(coroutine) :async def 定义,调用后返回协程对象,不会立即执行。
  • 事件循环(event loop):调度所有就绪任务,是异步运行的中枢。
  • await:把控制权交还事件循环,遇到 I/O 时才真正让出,因此必须使用异步版库。
  • 并发原语 :asyncio.gather 批量并发;Semaphore 限流;asyncio.timeout 控制总耗时。

一个关键原则:不要在协程里写阻塞调用 。requests、time.sleep、同步数据库驱动都会阻塞整个事件循环,把并发全部拖死。

三、代码演示

python 复制代码
import asyncio, aiohttp, time

URLS = [f"[https://httpbin.org/delay/1?n={i}](https://httpbin.org/delay/1?n={i})" for i in range(30)]
CONCURRENCY = 10          # 单域名并发上限,避免压垮对端

async def fetch(session, url, sem):
    async with sem:                       # 限流
        async with session.get(url, timeout=5) as resp:
            return await resp.json()

async def main():
    sem = asyncio.Semaphore(CONCURRENCY)
    timeout = aiohttp.ClientTimeout(total=5)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        tasks = [fetch(session, u, sem) for u in URLS]
        results = await asyncio.gather(*tasks, return_exceptions=True)
    ok = [r for r in results if not isinstance(r, Exception)]
    print(f"成功 {len(ok)} / {len(URLS)}")
    return ok

if __name__ == "__main__":
    start = time.time()
    asyncio.run(main())
    print(f"总耗时 {time.time() - start:.2f}s")   # 约 3.2s

耗时从 30s 降到 3s 左右,本质是把「串行等待」改成了「并行等待」。

再加一层超时兜底与重试,生产环境更稳:

python 复制代码
async def fetch_with_retry(session, url, sem, retry=2):
    for attempt in range(retry + 1):
        try:
            async with sem:
                async with asyncio.timeout(5):
                    async with session.get(url) as resp:
                        resp.raise_for_status()
                        return await resp.json()
        except (asyncio.TimeoutError, aiohttp.ClientError):
            if attempt == retry:
                raise
            await asyncio.sleep(2 ** attempt)   # 指数退避

四、避坑清单

  1. 同步库混用 :非要用 requests 时,用 asyncio.to_thread() 包一层,别直接调用。
  2. 无上限并发 :gather 一次性展开三千个任务会打爆连接池,必须加 Semaphore。
  3. 异常吞没 :gather 默认遇到异常即抛出,其余任务被取消;需要全部结果时加 return_exceptions=True。
  4. CPU 密集任务 :异步无收益,应交给 ProcessPoolExecutor。
  5. 资源未释放 :ClientSession 必须用 async with 管理,否则连接泄漏。

五、小结

asyncio 的收益来自 I/O 等待占比。判断标准很直接:任务耗时是否主要花在等网络、等磁盘、等数据库。如果是,异步能把等待时间重叠起来;如果主要花在计算,异步只会增加复杂度。落地时先量化单次请求耗时与并发上限,再决定协程数量,比盲目堆并发更可靠。

海量精选技术文档和实战案例持续更新,敬请关注【风骏时光少年】

相关推荐
IT_陈寒34 分钟前
Redis雪崩把我坑惨了,这是5年经验的教训
前端·人工智能·后端
SameX44 分钟前
HarmonyOS ArkUI List 的侧滑删除 用 ListItem 的 swipeAction + endIcon 的做法
前端
usdoc文档预览3 小时前
USDOC系列之PPT文件在线编辑-VUE前端实现在线浏览器在线编辑PPT文档
前端·vue.js·编辑器·office365·ppt在线编辑·office文件在线编辑·文件在线编辑
用户2705077725243 小时前
Agent 调工具卡住?3 种超时与取消策略实测对比
前端
LuHang4 小时前
我一行代码没写,把领导派的活做成了公司内部系统😭
前端
明月_清风4 小时前
Meta 开源了一个 AI Native 组件库:Astryx,150+ React 组件,专为 AI Agent 而设计
前端·人工智能·ai编程
用户850869276155 小时前
秒杀系统中 Shell 脚本并发控制 Bug:究竟哪里出了
前端
炫酷的大高个7 小时前
6 亿播放的闪身步,我用前端算出 15 倍重心差
前端·javascript
不可能片场7 小时前
内容运营自动化 配图折行别把词切两半
前端·electron