Python 异步编程async/await与 FastAPI 核心知识笔记
1. async/await 核心概念
1.1 核心作用
-
async def:定义一个协程函数。调用时不会立即执行,而是返回一个协程对象,需要由事件循环驱动。 -
await:挂起当前协程,将控制权交还给事件循环,直到等待的异步操作完成。它是实现“让出CPU”的关键。
1.2 关键认知纠正
| 常见误解 | 正确理解 |
|---|---|
async 代表“耗时操作” |
async 代表函数内有 “可等待的 I/O 操作”,适合网络请求、数据库查询等。耗时计算(CPU密集型)应使用普通 def。 |
async 函数会自动并发 |
只有遇到 await 时才会让出控制权,否则会同步执行。 |
调用 async 函数必须加 await
|
不加 await 不会报错,但函数体不会执行,只返回一个协程对象。 |
1.3 await 的使用场景
-
在
async def内部调用另一个异步操作(必须)。 -
并发执行多个 I/O 任务(如
asyncio.gather)。 -
等待异步库的 I/O 操作(如
aiohttp、asyncpg)。
2. FastAPI 中的并发模型与接口定义
2.1 FastAPI 的底层架构
-
Worker 进程:通过
--workers可启动多个进程,利用多核CPU。 -
事件循环:每个 Worker 进程内部有一个单线程的事件循环,负责调度所有
async接口。
2.2 接口阻塞的原因
“一个接口慢,其他接口排队” 是因为事件循环被阻塞。常见原因:
- 在
async def接口中调用了同步阻塞代码(如time.sleep、requests.get)。 - 在
async def接口中执行了CPU密集型计算。
2.3 如何选择接口定义方式(async def vs def)
| 接口内部逻辑 | 推荐定义方式 | 原因 |
|---|---|---|
I/O 密集型,且使用支持 await 的异步库 |
async def |
遇到 await 时让出事件循环,提升并发。 |
| 只有简单返回,或仅做参数校验 | def |
避免创建协程的开销,性能更优。 |
调用同步阻塞库(如 requests、psycopg2) |
def |
FastAPI 会自动放到线程池,不阻塞主事件循环。 |
| CPU 密集型计算(图像处理、模型推理) |
def + run_in_threadpool
|
将任务交给线程池,避免阻塞。 |
2.4 核心铁律
如果代码里没有
await,就别用async def。
异步不是“银弹”,要为了“有await可写”而用async。
3. 数据库查询与 async 的选择
3.1 决策依据
是否使用 async 取决于数据库驱动是否支持异步,与查询速度无关。
| 驱动类型 | 推荐接口定义 | 示例库 |
|---|---|---|
| 异步驱动 |
async def + await
|
asyncpg, aiomysql, asyncmy, SQLAlchemy 异步支持
|
| 同步驱动 | def |
psycopg2, mysql-connector-python
|
3.2 错误示例
python
@app.get("/bad")
async def bad(): # ❌ 用了 async
conn = psycopg2.connect() # ❌ 同步驱动
cur = conn.cursor()
cur.execute("SELECT...") # ⚠️ 会阻塞事件循环!
return cur.fetchall()
3.3 正确示例
python
# ✅ 异步驱动
@app.get("/users")
async def get_users():
async with async_session() as session:
result = await session.execute(select(User))
return result.scalars().all()
python
# ✅ 同步驱动
@app.get("/users")
def get_users():
conn = psycopg2.connect(...)
cur = conn.cursor()
cur.execute("SELECT * FROM users")
return cur.fetchall()
4. Python 异步 vs Java 多线程
| 维度 | Python (asyncio) | Java (传统多线程) | Java (虚拟线程) |
|---|---|---|---|
| 底层模型 | 单线程 + 事件循环 | N 个 OS 线程(重量级) | M 个虚拟线程挂载于 N 个 OS 线程 |
| CPU 密集型 | 差(只用1核) | 优秀(用满多核) | 优秀(用满多核) |
| I/O 密集型(高并发) | 优秀(切换开销极小) | 差(线程多时切换开销大) | 极优秀(极轻量,自动调度) |
| 编程复杂度 | 高(async/await 传染) |
中等(用线程池管理) | 低(写同步代码即可) |
4.1 核心结论
- “多”不一定好:对于 I/O 密集型任务,Python 的单线程+事件循环模型往往比 Java 旧版重量级线程池更高效。
-
“并行”不等同于“并发”:
async解决的是并发(同时处理多个连接),而非并行(同时执行多个计算)。
5. 关键记忆点
-
async def:告诉 Python,“我这个函数里面可能有await,允许我在等待时被切换出去。” -
await:告诉事件循环,“我现在要去等一个结果(网络/数据库),你先去忙别的,有结果了叫我。” - 异步不是让代码跑得更快,而是让服务在等待 I/O 时能处理更多请求。
-
async是“并发”的语法糖,目的是在 I/O 等待时最大化 CPU 利用率。