Python 多进程适用场景分析

1次阅读

cpu密集型任务必须用multiprocessing,因cpython的gil使threading无法加速计算;io密集型则优先选threading或asyncio。

Python 多进程适用场景分析

什么时候该用 multiprocessing,而不是 threading

CPU 密集型任务必须用 multiprocessing,因为 threading 在 CPython 下受 GIL 限制,线程跑计算几乎不提速。

典型场景:图像批量处理、数值模拟、加密解密、大量本地数据聚合(比如用 pandas 做 groupby+apply)。

  • IO 密集型(如发 http 请求、读写文件)反而优先选 threadingasynciomultiprocessing 启动开销大,进程间通信成本高
  • 如果你的“多进程”代码跑得比单进程还慢,大概率是任务太轻量,或者频繁在进程间传大数据(比如把整个 df 传给每个子进程)
  • multiprocessing.Pool 默认启动和 CPU 核数一样多的进程,但不是越多越好——内存占用会线性增长,linux 下还可能触发 OOM Killer

Pool.mapPool.apply_async 怎么选

Pool.map 简单直接,适合输入可迭代、函数无副作用、结果顺序必须严格对应输入顺序的场景;apply_async 更灵活,但得自己管理回调和异常。

  • Pool.map 会阻塞直到全部完成,中间一个出错(比如某次计算抛 ZeroDivisionError),整个调用就崩,错误也难定位到具体是第几个输入项
  • apply_async 可以配合 error_callback 捕获子进程异常,也能用 get(timeout=...) 控制等待时间,避免卡死
  • 如果输入数据量极大(比如千万级 ID),别一次性 map 全部,改用 imapimap_unordered 流式处理,内存更稳

子进程拿不到主进程的变量或类实例

这是最常被卡住的点:子进程是全新 Python 解释器实例,不会自动继承主进程的全局变量、已导入模块状态、数据库连接、日志 handler 等。

立即学习Python免费学习笔记(深入)”;

  • 所有需要的数据,必须显式通过参数传入——注意 listdict 这类可变对象会被序列化(pickle),传过去的是副本,改了不影响主进程
  • 不要试图在子进程中调用 Logging.info 期望它输出到主进程的 logger——得重新配置,或统一用 print + 主进程收集 stdout
  • torchtensorflow 这种底层库,若主进程已初始化 CUDA 上下文,子进程里再 init 很可能报 cudaErrorInitializationError,得在子进程里单独处理设备逻辑

windows 下启动失败或卡在 “Starting processes…”

Windows 没有 forkmultiprocessing 必须走 spawn 方式重载模块,所以入口代码必须包在 if __name__ == '__main__': 里,否则子进程会递归启动新进程。

  • 漏写这个判断,程序可能无限 fork,CPU 占满,任务没跑起来就挂了
  • 如果用了 pyinstaller 打包,还要额外加 --multiprocessing-fork 参数,不然打包后运行照样卡住
  • 某些 ide(比如 pycharm 的默认配置)调试时会干扰 spawn,建议先命令行运行验证逻辑

事情说清了就结束。真正难的不是怎么写 multiprocessing,而是判断“这活儿到底值不值得拆成多进程”——多数时候瓶颈其实在磁盘 IO 或算法本身,而不是 CPU 核数。

text=ZqhQzanResources