多处理 fork() 与 spawn()

Cry*_*ina 18 python fork multiprocessing spawn python-multiprocessing

我正在阅读python 文档中对两者的描述:

产卵

父进程启动一个新的 python 解释器进程。子进程将只继承运行进程对象 run() 方法所需的资源。特别是,父进程中不必要的文件描述符和句柄将不会被继承。与使用 fork 或 forkserver 相比,使用此方法启动进程相当慢。[在 Unix 和 Windows 上可用。Windows 和 macOS 上的默认设置。]

叉子

父进程使用 os.fork() 来派生 Python 解释器。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全地分叉多线程进程是有问题的。[仅在 Unix 上可用。Unix 上的默认设置。]

我的问题是:

  1. 是不是分叉更快了,因为它不会尝试识别要复制的资源?
  2. 是不是因为 fork 复制了所有内容,与 spawn() 相比,它会“浪费”更多资源吗?

Jer*_*101 19

3 种多处理启动方法之间存在权衡:

  1. fork更快,因为它对父进程的整个虚拟内存进行写时复制,包括初始化的 Python 解释器、加载的模块和内存中的构造对象。

    但是 fork 不会复制父进程的线程。因此,在父进程中由其他线程持有的锁(在内存中)被卡在子进程中而没有拥有线程来解锁它们,当代码试图获取它们中的任何一个时,准备好导致死锁。此外,任何带有分叉线程的本机库都将处于损坏状态。

    复制的 Python 模块和对象可能有用,或者它们可能不必要地使每个分叉的子进程膨胀。

    子进程还“继承”操作系统资源,如打开的文件描述符和打开的网络端口。这些也可能导致问题,但 Python 可以解决其中的一些问题。

    所以fork 速度快,不安全,而且可能臃肿。

    然而,这些安全问题可能不会引起问题,具体取决于子进程的作用。

  2. 产卵从头开始一个 Python 子进程,没有父进程的内存、文件描述符、线程等。加载目标模块并运行目标可调用。

    因此spawn 安全、紧凑且速度较慢,因为 Python 必须加载、初始化自身、读取文件、加载和初始化模块等。

    但是,与子进程所做的工作相比,它可能不会明显变慢

  3. forkserver分叉当前 Python 进程的副本,该副本将缩减为大约一个全新的 Python 进程。这成为“分叉服务器”过程。然后每次启动子进程时,它都会要求 fork 服务器 fork 一个子进程并运行其目标可调用。

    这些子进程一开始都是紧凑的,没有卡住的锁。

    forkserver 更复杂,而且没有很好的文档记录。Bojan Nikolic 的博客文章详细解释了 forkserver 及其set_forkserver_preload()预加载某些模块的秘密方法。小心使用未记录的方法,尤其是。在 Python 3.7.0 中错误修复之前。

    所以forkserver 是快速、紧凑和安全的,但它更复杂并且没有很好的文档记录

[文档在所有这些方面都不是很好,所以我结合了来自多个来源的信息并做出了一些推断。对任何错误发表评论。]

  • @michalmonday,如果父进程在分叉子进程时是单线程的,那么“fork”选项会更安全。所以是的,在启动其他线程之前尽早分叉其他(子)进程。我不知道“fork”有任何其他安全问题。 (4认同)
  • @LieRyan 事实上,如果这些页面没有被使用,它们不会占用 RAM 空间,但它们会添加到子进程的地址空间,这可能会使其更接近内存不足杀手。此外,添加/删除对这些页面中任何 Python 对象的引用都会更新其引用计数,因此需要复制其页面。Python 的循环检测 GC 可能需要扫描这些页面,从而将它们交换到 RAM 中并消耗 GC 工作量。 (2认同)
  • @Jerry101如果需要更新引用计数,那么是的,可能需要复制页面,但这仅意味着该模块已实际使用。另一方面,无论是否使用该模块,多处理生成方法始终都会复制该模块。尽管有引用计数和 GC,fork 需要复制的内容仍然比使用 spawn 时要少得多。 (2认同)
  • 多处理生成与子进程生成不同。通过子进程生成,您将生成一个不同的 Python 程序,该程序可以具有不同的(并且希望更小的)加载模块列表。但对于多处理生成,初始化将预加载主进程中加载​​的所有模块,因此它总是比 fork 更臃肿。 (2认同)

Dar*_*aut 9

  1. 是不是分叉更快了,因为它不会尝试识别要复制的资源?

是的,它要快得多。内核可以克隆整个进程,并且只将修改后的内存页作为一个整体进行复制。不需要将资源输送到新进程并从头启动解释器。

  1. 是不是因为 fork 复制了所有内容,与 spawn() 相比,它会“浪费”更多资源吗?

现代内核上的 fork 只执行“写时复制”,它只影响实际更改的内存页面。需要注意的是,“写入”已经包含仅迭代 CPython 中的对象。那是因为对象的引用计数增加了。

如果您的进程长时间运行并使用大量小对象,这可能意味着您比 spawn 浪费更多内存。有趣的是,我记得 Facebook 声称通过将其 Python 进程从“fork”切换到“spawn”,显着减少了内存使用。

  • @Kimi spawn:Windows、macOS 上的 Python 3.8+;fork:Unix,包括带有 Python<3.8 的 macOS (2认同)