不能直接在async函数中执行cpu密集型任务,因会阻塞事件循环;必须用processpoolexecutor配合loop.run_in_executor()移至子进程,并加超时、异常处理及pickle约束。

不能直接在 async 函数里跑 CPU 密集型计算,否则事件循环会卡死;必须用 ProcessPoolExecutor + loop.run_in_executor() 拆到子进程,并做好超时、异常和序列化约束的兜底。
为什么 await cpu_bound_func() 会报错或卡死
await 只接受 Awaitable 对象(比如协程、Future),而普通函数(如 sum(range(10**8)))不是。即使你强行包一层 run_in_executor,若没设超时,一旦子进程死循环或崩溃,主线程就彻底 hang 住——asyncio.sleep(0) 都插不进调度。
- 现象:CPU 占满但并发数为 1,多个
create_task实际串行执行 - 根本原因:GIL 未被绕过,纯计算阻塞当前线程,事件循环无法切换
- 误区:以为
asyncio.to_thread()能提速 CPU 任务——它只是换线程,仍受 GIL 限制
必须用 ProcessPoolExecutor,且函数要满足 pickle 条件
ThreadPoolExecutor 对 CPU 密集型任务无效,4 核机器上开 4 个线程算斐波那契,耗时几乎等于单线程。真正并行只能靠多进程,但前提是传入的函数和参数能被 pickle 序列化。
- ✅ 支持:模块顶层定义的函数、
int/str/dict等基本类型、@dataclass(无方法) - ❌ 不支持:
lambda、闭包、类实例方法(如obj.method)、含文件句柄或数据库连接的对象 - Windows 注意:
spawn启动方式要求入口函数必须在if __name__ == "__main__":下保护,否则反复 fork
如何安全调用并处理错误
别每次新建 ProcessPoolExecutor,也别在 run_in_executor 外层 try/catch——子进程异常不会以原样抛出,而是变成 BrokenProcessPool 或 TimeoutError 等间接形式。
- 复用
executor:用with ProcessPoolExecutor(max_workers=2) as executor:管理生命周期 - 必加超时:用
asyncio.wait_for(loop.run_in_executor(...), timeout=30),避免死锁 - 捕获具体异常:
except concurrent.futures.ProcessPoolExecutor.BrokenProcessPool:和except asyncio.TimeoutError: - 并发多个任务:用
await asyncio.gather(*[loop.run_in_executor(...) for _ in range(5)]),它们真并行
容易被忽略的边界细节
所有参数和返回值都会经历一次 pickle.dumps() → 进程间传输 → pickle.loads()。这意味着:
- 大对象(如百 MB 的
numpy.ndarray)传参开销可能超过计算本身 - 返回值过大可能触发
MemoryError或 IPC 超时,建议只传 ID 或路径,让子进程自己加载 -
max_workers别盲目设成os.cpu_count(),尤其当任务本身内存占用高时,反而引发频繁 swap
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











