主题
Python 异步编程
本文讲解基于 asyncio 的协程模型:事件循环、async/await、任务并发与常见陷阱。
作者:yanshaodong
为什么需要异步
同步代码中遇到 I/O(网络、磁盘)时线程会阻塞等待。异步通过协程在等待期间让出控制权,从而以单线程并发处理大量 I/O 任务,提升吞吐。
协程基础
python
import asyncio
async def fetch_data():
await asyncio.sleep(1) # 模拟 I/O,让出控制权
return "data"
async def main():
data = await fetch_data()
print(data)
asyncio.run(main())async def定义协程函数,调用返回协程对象而非立即执行。await挂起当前协程,直到可等待对象完成。asyncio.run()创建事件循环并运行顶层协程。
并发执行任务
python
async def task(name, delay):
await asyncio.sleep(delay)
return f"{name} 完成"
async def main():
# 并发:同时启动,总耗时取最长
results = await asyncio.gather(
task("A", 1),
task("B", 2),
task("C", 3),
)
print(results) # ['A 完成', 'B 完成', 'C 完成']gather 并发运行多个协程,约 3 秒完成(而非 1+2+3)。
Task 与 create_task
python
async def main():
t1 = asyncio.create_task(task("A", 1))
t2 = asyncio.create_task(task("B", 2))
await t1
await t2create_task 立即调度协程为任务,适合在 await 之前就启动。
超时与取消
python
async def main():
try:
await asyncio.wait_for(task("X", 5), timeout=2)
except asyncio.TimeoutError:
print("超时")
t = asyncio.create_task(task("Y", 10))
t.cancel()
try:
await t
except asyncio.CancelledError:
print("已取消")同步原语
python
lock = asyncio.Lock()
async def safe_inc():
async with lock:
# 临界区,同一时刻仅一个协程进入
...还提供 asyncio.Event、asyncio.Queue、asyncio.Semaphore 等。
异步上下文管理器与迭代器
python
class Conn:
async def __aenter__(self):
print("连接")
return self
async def __aexit__(self, *exc):
print("关闭")
async def main():
async with Conn():
await asyncio.sleep(0.1)与同步代码互操作
- 在同步函数中调用异步:
asyncio.run(coro)或loop.run_until_complete。 - 在异步中运行阻塞调用:用
asyncio.to_thread(blocking_func)避免阻塞事件循环。 - 不要在协程里用同步
time.sleep(),应使用await asyncio.sleep()。
常见陷阱
- 忘记 await:协程对象不执行,仅被创建。
- 混用阻塞 I/O:在事件循环中调用同步阻塞函数会卡住所有协程。
- 在同步函数里直接调用协程:需通过
asyncio.run或事件循环驱动。
小结
- 异步适合 I/O 密集型,不适合 CPU 密集型(用多进程)。
async/await是语法糖,底层是事件循环调度协程。gather/create_task实现并发;wait_for控制超时。- 阻塞调用务必
to_thread或异步库(如aiohttp、asyncpg)。