多处理 fork() 与 spawn()

multiprocessing fork() vs spawn()

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

spawn

The parent process starts a fresh python interpreter process. The child process will only inherit those resources necessary to run the process objects run() method. In particular, unnecessary file descriptors and handles from the parent process will not be inherited. Starting a process using this method is rather slow compared to using fork or forkserver. [Available on Unix and Windows. The default on Windows and macOS.]

fork

The parent process uses os.fork() to fork the Python interpreter. The child process, when it begins, is effectively identical to the parent process. All resources of the parent are inherited by the child process. Note that safely forking a multithreaded process is problematic. [Available on Unix only. The default on Unix.]

我的问题是:

  1. 分叉是否更快,因为它不会尝试识别要复制的资源?
  2. 是不是因为 fork 复制所有内容,与 spawn() 相比,它会“浪费”更多的资源?
  1. is it that the fork is much quicker 'cuz it does not try to identify which resources to copy?

是的,速度快多了。内核可以克隆整个过程,只复制修改后的memory-pages作为一个整体。不需要将资源通过管道传输到新进程并从头启动解释器。

  1. is it that, since fork duplicates everything, it would "waste" much more resources comparing to spawn()?

现代内核上的 Fork 只做 "copy-on-write" 并且它只影响实际改变的 memory-pages。需要注意的是,“写入”已经包含仅迭代 CPython 中的对象。那是因为对象的 reference-count 增加了。

如果您有很长的 运行 进程并在使用大量小对象,这可能意味着您浪费的内存比使用 spawn 多。有趣的是,我记得 Facebook 声称他们的 Python-processes.

从“fork”切换到“spawn”后大大减少了 memory-usage

3 multiprocessing start methods:

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

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

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

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

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

    然而,这些安全问题可能不会造成麻烦,具体取决于子进程的行为。

  2. spawn 从头开始​​一个 Python 子进程,没有父进程的内存、文件描述符、线程等。从技术上讲,spawn forks a当前进程的副本,然后子进程立即调用 exec 用新的 Python 替换自己,然后要求 Python 加载目标模块和 运行 目标可调用对象。

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

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

  3. forkserver 分叉当前 Python 进程的副本,该进程精简为大约新的 Python 进程。这成为“fork server”进程。然后每次启动子进程时,它都会要求 fork 服务器 fork 一个子进程和 运行 它的目标可调用对象。

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

    forkserver 更复杂并且没有很好的文档记录。 Bojan Nikolic's blog post explains more about forkserver and its secret set_forkserver_preload() method to preload some modules. Be wary of using an undocumented method, esp. before the bug fix in Python 3.7.0.

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

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