本文深入剖析 Python 中 async/await 的本质——它实现的是协程驱动的协作式并发,而非自动并行化;关键在于函数内部是否包含可挂起的异步操作(如 await asyncio.sleep()),而非仅靠 async def 声明。
本文深入剖析 python 中 `async`/`await` 的本质——它实现的是协程驱动的**协作式并发**,而非自动并行化;关键在于函数内部是否包含可挂起的异步操作(如 `await asyncio.sleep()`),而非仅靠 `async def` 声明。
在机器学习服务场景中,我们常需对输入数据进行预处理(preprocess),再并行执行模型预测(predict)和业务逻辑(apply_business_logic),最后加权融合结果。这种“一前两后、后两者独立”的结构,天然适合异步优化——但能否真正并发,取决于函数是否真正让出控制权。
❌ 错误认知:async def = 自动异步
许多开发者误以为只要将函数声明为 async def,调用时加上 await,就实现了并发。实则不然。看以下典型反例:
import asyncio
async def predict(x: list[int]) -> float:
await asyncio.sleep(3) # ✅ 可挂起:释放事件循环
return sum(x) / len(x)
async def apply_business_logic(x: list[int]) -> int:
time.sleep(2) # ❌ 同步阻塞:独占线程,事件循环被冻结!
return sum(x) ** 2
async def bad_main(x: list[int]):
x = preprocess(x)
# 以下仍是串行执行!因为 apply_business_logic 内部无 await
pred = await predict(x)
logic = await apply_business_logic(x) # 等待 2 秒期间,predict 完全无法运行
return 0.7 * pred + 0.3 * logic
此处 apply_business_logic 虽标记为 async def,但内部使用 time.sleep()——这是一个同步阻塞调用,会直接冻结当前线程,导致整个事件循环停滞。此时 await predict(x) 和 await apply_business_logic(x) 实际是严格串行执行(总耗时 ≈ 3s + 2s = 5s),与同步版本无异。
✅ 正确实践:await + 可挂起操作 = 真正并发
要实现并发,必须确保所有耗时操作都是异步友好的(即内部含 await)或通过线程/进程池托管:
方案 1:纯异步(推荐用于 I/O 密集型)
import asyncio
def preprocess(x: list[int]) -> list[int]:
return x # 同步函数,无需 await
async def predict(x: list[int]) -> float:
await asyncio.sleep(3) # 模拟异步模型推理(如 HTTP 请求、数据库查询)
return sum(x) / len(x)
async def apply_business_logic(x: list[int]) -> int:
await asyncio.sleep(1) # 模拟异步规则引擎调用
return sum(x) ** 2
async def main(x: list[int]) -> float:
x = preprocess(x)
# ✅ 并发启动两个协程
pred_result, logic_result = await asyncio.gather(
predict(x),
apply_business_logic(x)
)
return 0.7 * pred_result + 0.3 * logic_result
# 运行
if __name__ == "__main__":
x = list(range(100))
result = asyncio.run(main(x)) # 总耗时 ≈ max(3s, 1s) = 3s(非 4s!)
print(">> final result:", result)
? asyncio.gather() 是并发执行多个协程的正确方式:它同时调度所有任务,当某协程遇到 await 时,事件循环立即切换到其他就绪任务。
方案 2:混合同步阻塞操作(适用于 CPU 密集型或遗留代码)
若 apply_business_logic 必须使用 time.sleep() 或计算密集型逻辑,需借助线程池解耦:
import asyncio
from concurrent.futures import ThreadPoolExecutor
def sync_apply_business_logic(x: list[int]) -> int:
time.sleep(2) # 同步阻塞操作
return sum(x) ** 2
async def main_with_executor(x: list[int]) -> float:
x = preprocess(x)
# 在线程池中执行同步函数,返回可 await 的 Future
loop = asyncio.get_running_loop()
with ThreadPoolExecutor() as pool:
pred_task = predict(x)
logic_task = loop.run_in_executor(pool, sync_apply_business_logic, x)
pred_result, logic_result = await asyncio.gather(pred_task, logic_task)
return 0.7 * pred_result + 0.3 * logic_result
⚠️ 关键注意事项
- async def 仅定义协程对象,不执行:调用 predict(x) 返回的是协程对象(coroutine object),必须 await 或传入 asyncio.gather() 才真正调度。
- 避免在协程中调用任何同步阻塞函数(如 time.sleep, requests.get, 文件读写等),除非明确使用 run_in_executor。
- CPU 密集型任务慎用 asyncio:asyncio 擅长 I/O 并发,但无法加速纯计算。对 ML 推理等 CPU 密集场景,应优先考虑 multiprocessing 或专用加速库(如 ONNX Runtime 的异步 API)。
- 始终用 asyncio.run() 启动主协程(Python 3.7+),避免手动管理事件循环(如已弃用的 loop.run_until_complete)。
总结
async/await 不是魔法开关,而是协作式并发的契约:每个 await 都是一次主动让权,允许事件循环调度其他任务。真正的异步效率提升,源于函数内部对 await 的合理使用,而非语法糖本身。在构建 ML 服务时,明确区分 I/O 等待(用 asyncio)与 CPU 计算(用线程/进程池),才能写出既高效又健壮的异步代码。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











