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) # 指数退避
四、避坑清单
- 同步库混用 :非要用
requests时,用asyncio.to_thread()包一层,别直接调用。 - 无上限并发 :
gather一次性展开三千个任务会打爆连接池,必须加Semaphore。 - 异常吞没 :
gather默认遇到异常即抛出,其余任务被取消;需要全部结果时加return_exceptions=True。 - CPU 密集任务 :异步无收益,应交给
ProcessPoolExecutor。 - 资源未释放 :
ClientSession必须用async with管理,否则连接泄漏。
五、小结
asyncio 的收益来自 I/O 等待占比。判断标准很直接:任务耗时是否主要花在等网络、等磁盘、等数据库。如果是,异步能把等待时间重叠起来;如果主要花在计算,异步只会增加复杂度。落地时先量化单次请求耗时与并发上限,再决定协程数量,比盲目堆并发更可靠。
海量精选技术文档和实战案例持续更新,敬请关注【风骏时光少年】