Python的os.fork是否使用同一个Python解释器?

12

我知道在Python中,线程使用同一个Python解释器实例。我的问题是,在使用os.fork创建进程时,是否也一样?或者每个由os.fork创建的进程都有自己的解释器?

3个回答

15
无论何时进行fork操作,整个Python进程都会在内存中复制一份(包括Python解释器、您的代码和任何库、当前堆栈等),以创建第二个进程 - 这就是为什么fork操作比创建线程要昂贵得多的原因之一。

这样就会创建一个新的副本的Python解释器。

两个Python解释器同时运行的一个优点是它们现在具有两个GIL(全局解释器锁),因此可以在多核系统上实现真正的多处理。

同一进程中的线程共享相同的GIL,意味着一次只能运行一个线程,只能给出并行的错觉。


14

fork确实会创建当前Python解释器的副本而不是在相同的解释器中运行,但通常这并不是您想要的,至少它不能单独使用。它存在以下问题:

  • 在某些平台上分叉多线程进程可能会导致问题。有些库(最著名的是苹果的Cocoa/CoreFoundation)可能会在后台为您启动线程,或者在您只有一个线程时使用线程本地API等,而不告诉您。
  • 一些库假定每个进程都会被正确初始化,但如果在初始化之后执行fork,那就不是真的了。最著名的是,如果让ssl在主进程中种子PRNG,然后再fork,那么您现在可能会得到可预测的随机数,这是安全漏洞。
  • 打开的文件描述符会被子进程继承(作为副本),在各个平台上的细节有所不同,很让人头疼。
  • POSIX只要求在forkexec之间实现一组非常具体的系统调用。如果您从未调用过exec,则只能使用这些系统调用。这基本上意味着您无法在不同平台上做任何事情。
  • 任何关于信号的操作在fork之后都变得非常麻烦和不可移植。

有关这些问题的详细信息,请参见POSIX fork或您所用平台的手册。

正确的答案几乎总是使用multiprocessing,或者使用concurrent.futures(它包装了multiprocessing)或类似的第三方库。

从3.4版本开始,你甚至可以指定一个启动方法。使用fork方法基本上只是调用了fork。而使用forkserver方法则会运行一个单一的“干净”的进程(没有线程、信号处理程序、SSL初始化等),并从该进程派生新的子进程。使用spawn方法会调用fork然后exec,或者类似于posix_spawn的等效方法,以获取全新的解释器而不是副本。因此,你可以先用fork开始,但如果遇到任何问题,切换到forkserverspawn,而无需更改代码中的其他内容。这相当不错。


1
对于“盲目地fork()”的许多警告,这是一个不错的回应 :) - James Mills
@JamesMills:现在你把“盲目”和“分支”放在一起,我脑子里就开始响起Angry Samoans的 Lights Out ,而它不在我的iTunes Match上。谢了 : P - abarnert
哈哈哈,抱歉!:) 尽管这是以某人为代价的玩笑,但双关语仍然很有趣 :) - James Mills
Fork然后exec似乎在线程锁定方面是安全的,尽管我也听说过它不安全。我认为文档对于使用多进程时线程锁定会发生什么是沉默的 - 它只提到了multiprocessing.Process.Lock。 - user48956
@user48956 你是在询问线程本地变量 (如果它们能运行,那么应该是安全的,因为线程本地变量的整个意义就在于它们不被共享),还是线程锁?线程锁通常不会很安全,因为 POSIX 规定 pthread 同步对象不能跨进程使用(某些平台可能仍然可以使用它们,但没有充分的理由)。我认为文档没有提到这一点,因为从 multiprocessing.Lock 存在的事实中,你应该明白你应该使用它而不是 threading.Lock - abarnert
显示剩余3条评论

9

os.fork()相当于许多UNIX系统中的fork()系统调用。因此,是的,您的子进程将与父进程分开,并具有不同的解释器(因此)。

man fork

FORK(2)

NAME fork - create a child process

SYNOPSIS #include

   pid_t fork(void);

DESCRIPTION fork() creates a new process by duplicating the calling process. The new process, referred to as the child, is an exact duplicate of the calling process, referred to as the parent, except for the following points:

pydoc os.fork():

os.fork()派生一个子进程。在父进程中返回子进程的pid,在子进程中返回0。如果发生错误,则引发OSError。

请注意,使用fork()从线程时,包括FreeBSD <= 6.3,Cygwin和OS / 2 EMX在内的某些平台存在已知问题。

另请参阅:Martin Konecny的有关“分叉”的原因和优势的回复 :)

为简洁起见; 其他涉及不涉及单独进程和因此单独的Python解释器的并发方法包括:


需要注意的一点是:asyncio 实际上使用 yield-from 协程(尽管在 3.5 中,它可能会改用新的 async/await 协程)。值得一提的是,像 Twisted 这样更明确的回调驱动/未来驱动设计,以及直接循环遍历 selectorselect 的等效设计。或者使用 GUI 风格的事件循环。但我已经给你点了一个赞,所以你可能不会因为添加所有这些内容而获得额外的投票。 :) - abarnert
@abarnert 这是你想要的 :) 包含多种选择很好,我同意 :) - James Mills

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接