为什么在使用 joblib.Parallel 时保护主循环很重要?

Why is it important to protect the main loop when using joblib.Parallel?

joblib 文档包含以下警告:

Under Windows, it is important to protect the main loop of code to avoid recursive spawning of subprocesses when using joblib.Parallel. In other words, you should be writing code like this:

import ....

def function1(...):
    ...

def function2(...):
    ...

... if __name__ == '__main__':
    # do stuff with imports and functions defined about
    ...

No code should run outside of the “if __name__ == ‘__main__’” blocks, only imports and definitions.

最初,我认为这只是为了防止偶尔发生奇怪的情况,即传递给 joblib.Parallel 的函数递归调用模块,这意味着这通常是好的做法,但通常是不必要的。但是,我不明白为什么这只会对 Windows 造成风险。此外,this answer 似乎表明未能保护主循环导致代码 运行 比一个非常简单的非递归问题要慢几倍。

出于好奇,我 运行 来自 joblib 文档的令人尴尬的并行循环的超级简单示例,没有保护 windows 框上的主循环。在我关闭终端之前,我的终端一直收到以下错误消息:

ImportError: [joblib] Attempting to do parallel computing without protecting your import on a system that does not suppo
rt forking. To use parallel-computing in a script, you must protect you main loop using "if __name__ == '__main__'". Ple
ase see the joblib documentation on Parallel for more information

我的问题是, joblib 的 windows 实现在任何情况下都需要保护主循环怎么办?

如果这是一个非常基础的问题,我们深表歉意。我是并行化领域的新手,所以我可能只是缺少一些基本概念,但我找不到任何地方明确讨论过这个问题。

最后,我想说明一下,这纯属学术研究;我理解为什么这样写自己的代码是generally good practice,不管joblib如何,我都会继续这样做。

这是必要的,因为 Windows 没有 fork()。由于此限制,Windows 需要在它生成的所有子进程中重新导入您的 __main__ 模块,以便在子进程中重新创建父进程的状态。这意味着如果您有在模块级别生成新进程的代码,它将在所有子进程中递归执行。 if __name__ == "__main__" 守卫用于防止模块范围内的代码在子进程中重新执行。

这在 Linux 上没有必要,因为它 fork(),这允许它派生一个保持相同状态的子进程父级,无需重新导入 __main__ 模块。

万一有人在 2021 年偶然发现了这个问题: 由于 joblib>0.12 使用的新后端“loky”不再需要保护主 for 循环。参见 https://joblib.readthedocs.io/en/latest/parallel.html