Python3 asyncio 协程
多线程之外,asyncio 提供另一种并发方案——协程:在单线程内由事件循环调度多个任务,遇到 IO 时主动让出控制权,可轻松并发成百上千个连接,而代码写起来仍像同步那样顺序清晰。
async def 与 await
用 async def 定义的函数是协程函数:调用它并不立即执行,而是返回一个协程对象,需交给事件循环运行。协程内部用 await 等待另一个协程或可等待对象:
import asyncio
async def hello(): # 协程函数
print("开始")
await asyncio.sleep(1) # 模拟耗时,同时把控制权让出去
print("结束")
asyncio.run(hello()) # 事件循环入口:创建并运行
# 输出:开始 → 结束
asyncio.run:启动事件循环
asyncio.run(main()) 负责创建事件循环、运行 main 协程并在结束后关闭循环,是 Python 3.7+ 的推荐入口,脚本里只需调用一次。
依次 await 是串行
在 main 里逐个 await,任务仍是一个接一个执行,总耗时是各项之和:
import asyncio
async def task(name, sec):
await asyncio.sleep(sec)
print(name, "完成")
async def main():
await task("A", 1) # 串行:先等 A 完成
await task("B", 1) # 再等 B 完成,共约 2 秒
asyncio.run(main())
asyncio.gather:并发执行
把多个协程交给 asyncio.gather 即可并发调度,总耗时约等于最慢的一项,返回值按传入顺序排列:
import asyncio
import time
async def task(name, sec):
await asyncio.sleep(sec)
print(name, "完成")
return name
async def main():
t0 = time.perf_counter()
results = await asyncio.gather(task("A", 1), task("B", 2), task("C", 1))
print(results) # 输出:['A', 'B', 'C']
print(f"总耗时约 {time.perf_counter() - t0:.1f} 秒") # 输出:总耗时约 2.0 秒
asyncio.run(main())
与线程的对比
- 线程由操作系统调度,数量大时切换开销高且受 GIL 限制;协程由事件循环在单线程内调度,切换成本极低,可并发成千上万个 IO 任务;
- 协程代码更接近同步写法,没有加锁的烦恼;但计算密集任务两者都不理想,应使用多进程。
简单示例:并发请求
asyncio 通常配合 aiohttp 等异步库发起并发网络请求:
# 需安装依赖并配置环境后运行:pip install aiohttp
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as resp:
return resp.status
async def main():
async with aiohttp.ClientSession() as session:
urls = ["https://www.python.org", "https://www.baidu.com"]
codes = await asyncio.gather(*(fetch(session, u) for u in urls))
print(codes) # 输出:[200, 200](视网络环境而定)
asyncio.run(main())
小结:async def/await 声明协程,asyncio.run 启动事件循环,asyncio.gather 并发执行——单线程即可支撑海量 IO 连接,配合 aiohttp 能并发请求多个网络接口。