async函数内执行CPU密集任务会卡住事件循环,因async不解决CPU瓶颈;必须用ProcessPoolExecutor配合run_in_executor移至子进程,且需注意pickle限制、进程复用及Windows的spawn机制。

为什么不能直接在 async 函数里跑 CPU 密集型任务
因为 async 本身不解决 CPU 瓶颈——它只让 I/O 等待不阻塞事件循环。一旦你在 await 函数里写 sum(range(10**8)) 这类纯计算,整个 event loop 就卡住,其他协程全得干等。
常见错误现象:asyncio.sleep(0) 都无法插队执行;监控显示 CPU 占满但并发数为 1;asyncio.create_task() 启动的多个任务实际是串行执行。
- Python 的 GIL 让多线程对 CPU 密集型任务几乎无效,所以必须用多进程
-
run_in_executor默认用ThreadPoolExecutor,对 CPU 型任务没用,必须显式换ProcessPoolExecutor - 传给子进程的函数和参数必须能被
pickle,闭包、lambda、类实例方法(未绑定)通常不行
怎么用 run_in_executor 调起独立进程执行计算
核心是把计算逻辑抽成顶层函数,用 concurrent.futures.ProcessPoolExecutor 实例传给 loop.run_in_executor。
示例:计算斐波那契第 35 项(故意慢)
调用 Cutout.Pro 视觉处理 API 进行背景移除、人像抠图和照片增强,支持文件上传与图片 URL 输入。
立即学习“Python免费学习笔记(深入)”;
import asyncio
from concurrent.futures import ProcessPoolExecutor
<p>def cpu_bound(n):
if n <= 1:
return n
return cpu_bound(n-1) + cpu_bound(n-2)</p><p>async def main():
loop = asyncio.get_running_loop()</p><h1>注意:executor 必须复用,不要每次 new 一个</h1><pre class="brush:php;toolbar:false;">with ProcessPoolExecutor(max_workers=2) as executor:
# 这里真正进新进程
result = await loop.run_in_executor(executor, cpu_bound, 35)
print(result)asyncio.run(main())
-
max_workers别设太大,通常设为os.cpu_count()或略小,避免进程创建开销反超收益 - 别在
run_in_executor外部捕获异常——子进程报错会以concurrent.futures.ProcessPoolExecutor相关异常形式抛回主线程,比如BrokenProcessPool - 如果要并发跑多个 CPU 任务,用
asyncio.gather()包裹多个run_in_executor调用,它们会真正并行
参数传递和返回值的边界在哪里
所有传入 run_in_executor 的参数,以及返回值,都会被 pickle 序列化/反序列化一次。这意味着:
- 支持:基本类型(
int/str/list/dict)、纯数据类(dataclass且无方法)、模块级函数 - 不支持:文件对象、数据库连接、带 lambda 的闭包、未绑定的实例方法(如
obj.method)、含不可序列化属性的类实例 - 大数组建议用
numpy.ndarray+shared_memory或mp.Array替代直接传,否则序列化开销可能比计算还重
容易被忽略的资源泄漏和生命周期问题
ProcessPoolExecutor 不是“即用即弃”的工具——它内部维护进程池,频繁创建销毁会导致 fork 开销激增、句柄泄漏、甚至僵尸进程。
- 推荐做法:在应用启动时初始化一个全局
ProcessPoolExecutor实例,整个生命周期复用 - 别在异步生成器(
async def ... yield)或短命 task 里临时建 executor - 若用
with语句,确保__exit__被调用(即不能被return或异常跳过),否则子进程不会被 clean shutdown - Windows 上要注意:子进程启动方式是
spawn,入口模块必须有if __name__ == '__main__':保护,否则会无限 fork
真正麻烦的不是怎么切进程,而是怎么让进程活下来、传得准、退得干净——这些细节漏掉一个,就从“提速”变成“更卡”。

















