Python多进程启动慢的核心原因是fork或spawn方式导致子进程复制或重初始化大量资源。fork在Linux/macOS上因写时复制、C扩展不安全及logging锁引发延迟;spawn在Windows/macOS上需重复执行顶层代码和import,加重开销。

Python 多进程启动为什么这么慢?  第1张

Python 多进程启动慢,核心原因不是代码写得不好,而是 默认使用 fork 或 spawn 启动方式时,子进程要复制或重新初始化大量资源 —— 尤其是当主进程已经加载了大型模块、占用了较多内存、或启用了某些全局状态(如 logging、multiprocessing 的资源跟踪器)时。

fork 方式在 Linux/macOS 上的“隐形开销”

Linux/macOS 默认用 fork 创建子进程。看似快,但实际有隐患:

  • 如果主进程已分配大量内存(比如加载了大模型、大数组、缓存数据),fork 会触发写时复制(Copy-on-Write),但一旦子进程调用任何涉及 Python 解释器初始化的操作(如导入新模块、调用 multiprocessing 内部函数),就可能引发大量页面拷贝,拖慢启动;
  • 某些 C 扩展(如 numpy 初始化后、某些数据库驱动)在 fork 后状态不安全,multiprocessing 为保安全会主动做额外清理和重置,增加延迟;
  • logging 模块在 fork 后若未正确 reset,会导致子进程卡在锁或 handler 初始化上。

spawn 方式在 Windows/macOS 上更“干净”但更慢

Windows 和 macOS(Python ≥ 3.8 默认)强制用 spawn:完全新建 Python 解释器进程,再重新 import 主模块。这意味着:

  • 每次启动都要重新执行顶层代码(包括所有 import、全局变量初始化、甚至 if __name__ == '__main__': 下的逻辑);
  • 如果主模块顶部做了耗时操作(如读配置、连数据库、加载模型),每个子进程都会重复执行一遍;
  • import 链越深、第三方包越多(尤其带 C 扩展的),启动越慢。

真正拖慢的常见“陷阱”

这些看似合理的设计,在多进程下会放大启动成本:

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

  • 在模块顶层执行耗时初始化:例如直接写 model = load_large_model(),导致每个子进程都加载一次;
  • 未保护入口点:Windows/macOS 下没加 if __name__ == '__main__':,子进程会递归启动更多进程(表现为卡死或爆炸式创建);
  • 使用了 multiprocessing.Manager() 或共享对象:会额外启动一个服务进程并建立 IPC 连接,首次调用延迟明显;
  • 开启了 debug 级别日志或启用了 multiprocessing.util.get_logger():日志系统在子进程中重建 handler 代价不小。

怎么让多进程启动快一点?

关键原则:**让子进程尽可能“轻量启动”,把重活推迟到真正需要时做**:

  • 把模型加载、数据库连接、大文件读取等操作移到 worker 函数内部,或用惰性单例(首次调用才初始化);
  • 确保主模块顶部只有 import 和定义,所有初始化逻辑放进函数或 if __name__ == '__main__': 块中;
  • 显式指定启动方法:mp.set_start_method('fork')(Linux 可信环境)或 'spawn'(跨平台稳妥),避免自动探测带来的不确定性;
  • concurrent.futures.ProcessPoolExecutor(max_workers=N, mp_context=...) 替代裸用 Process,它复用进程、减少反复启停;
  • 禁用 multiprocessing 日志:mp.log_to_stderr(level=logging.WARNING) 或直接关闭 logger。