在多处理中,每个进程在 CPython 中都有自己的 GIL 是真的吗?这与创建新的运行时有何不同?

spr*_*ksh 6 python cpu cpython multiprocessing gil

有什么注意事项吗?我有几个与此相关的问题。

创建更多 GIL 的成本是多少?它与创建单独的 python 运行时有什么不同吗?一旦创建了新的 GIL,它是否会根据该过程的需要从头开始创建所有内容(对象、变量、堆栈、堆),或者创建当前堆中所有内容的副本并创建堆栈?(如果垃圾收集处理相同的对象,它们就会发生故障。)正在执行的代码片段是否也被复制到新的 CPU 内核?我也可以将一个 GIL 与一个 CPU 核心联系起来吗?

现在复制东西是一项相当占用 CPU 的任务(如果我错了,请纠正我),决定是否进行多处理的阈值是多少?

PS:我说的是 CPython,但请随意将答案扩展到您认为必要的任何内容。

spr*_*ksh 12

6个月后回顾这个问题,我觉得我可以澄清年轻时的自己的疑惑。我希望这对偶然发现它的人有所帮助。

是的,确实在多处理模块中,每个进程都有一个单独的 GIL,并且没有任何警告。但是问题中对运行时和GIL的理解是有缺陷的,需要纠正。

我将通过一系列陈述来消除疑虑/回答问题。

  1. Python 代码由 CPython 虚拟机运行(编译为 Cpython 字节码,然后解释该字节码)。这就是 python 运行时的构成。
  2. 当我们创建一个新进程时,会启动一个全新的 python 虚拟机(我们称之为 python 进程),其中包含堆栈和堆内存。
  3. 是的,这是一个成本高昂的过程,但成本并不算太高。因为python虚拟机是一段预编译为机器代码的C代码。从长远来看,在java中他们不使用多处理的原因是它会创建多个JVM,这将是可怕的,因为JVM需要大量内存,而且JVM不是像CPython那样预编译的机器代码。
  4. GIL 只是 python 虚拟机中的一段代码,它让 CPython 解释器一次只执行一行 CPython 字节码(或一条指令)。因此,所有与 GIL 创建和成本相关的问题都是愚蠢的。基本上目的是询问 CPython 虚拟机。
  5. 我可以将 1 个 GIL 与 1 个 CPU 核心关联起来吗?: 最好问一下1个Python进程是否可以与1个CPU核心相关?:不。内核的工作是决定进程正在运行哪个核心(并且哪个核心会不时变化,进程无法控制它)。唯一的问题是,在任何给定时间点,一个 python 进程不能在多个内核上运行,并且一个 python 进程只能执行 CPython 字节码中的一条指令(由于 GIL)。

核心中复制的内容以及操作系统如何尝试让进程保持其正在处理的核心本身是一个单独且非常深入的主题。

最后一个问题是一个主观问题,但根据所有这些理解,它基本上是一种成本效益比,可能因程序而异,并且可能取决于进程的 CPU 密集程度以及机器有多少个核心等。所以不能被概括化。


Ond*_* K. 5

第一个标题问题的简短回答是:是的。每个进程都有自己的全局解释锁。之后,事情就变得复杂了,这并不是 Python 的问题,而是底层操作系统的问题。

在 Linux 上,生成新进程应该multiprocessing比启动新的 Python 解释器(从头开始)更便宜:

  • 您是fork()父进程(旁注:clone()这些天实际上已使用),子进程已经拥有您的代码并以父进程地址空间的副本开始 - >因为您实际上正在生成正在运行的进程的另一个实例,不需要execve()(并且所有与之相关的开销)并重新填充其内容。
  • 实际上当我们说地址空间的复制时,它实际上并没有全部复制,而是使用写时复制;因此,除非您修改了它,否则根本不需要复制它。

出于这个原因,我的直觉是,多重处理几乎总是比从头开始启动一个全新的 Python 解释器更有效。毕竟,即使您启动了一个新的解释器(大概是从正在运行的进程),它也会首先执行 /fork()包括clone()父级地址空间的“复制”,然后再转移到execve().

但实际上这可能会有所不同,并且取决于您的底层操作系统如何处理新进程的创建及其内存管理。