Python多重处理模块的.join()方法到底在做什么?


110

了解Python 多重处理(来自PMOTW文章),并且希望澄清一下join()方法的。

2008年的旧教程中,它指出,没有p.join()以下代码中的调用,“子进程将处于空闲状态而不会终止,成为必须手动杀死的僵尸”。

from multiprocessing import Process

def say_hello(name='world'):
    print "Hello, %s" % name

p = Process(target=say_hello)
p.start()
p.join()

我添加的打印输出PID,以及一个time.sleep测试,并就我所知道的,在自己的进程终止:

from multiprocessing import Process
import sys
import time

def say_hello(name='world'):
    print "Hello, %s" % name
    print 'Starting:', p.name, p.pid
    sys.stdout.flush()
    print 'Exiting :', p.name, p.pid
    sys.stdout.flush()
    time.sleep(20)

p = Process(target=say_hello)
p.start()
# no p.join()

20秒内:

936 ttys000    0:00.05 /Library/Frameworks/Python.framework/Versions/2.7/Reso
938 ttys000    0:00.00 /Library/Frameworks/Python.framework/Versions/2.7/Reso
947 ttys001    0:00.13 -bash

20秒后:

947 ttys001    0:00.13 -bash

行为与p.join()在文件末尾添加回的行为相同。本周Python模块为模块提供了非常易读的解释 ; “要等到进程完成工作并退出后,请使用join()方法。”,但看来至少OS X仍在这样做。

我也想知道该方法的名称。该.join()方法在此处串联吗?它是在连接过程的结尾吗?还是只是与Python的本地.join()方法共享一个名称?


2
据我所知,它拥有主线程,并等待子进程完成,然后再加入主线程中的资源,主要是进行干净的退出。
abhishekgarg 2014年

啊,这是有道理的。那么,它CPU, Memory resources是与父进程分开的实际对象,然后join在子进程完成后再次返回吗?
MikeiLL 2014年

是的,这就是它的作用。因此,如果您不加入他们的行列,则子进程完成后,它只是处于已失效或已死进程的状态
abhishekgarg 2014年

@abhishekgarg事实并非如此。当主进程完成时,子进程将被隐式连接。
dano

@dano,我也在学习python,我只是分享了我在测试中发现的内容,在我的测试中,我有一个永无止境的主进程,所以也许这就是为什么我认为那些子进程已经失效。
abhishekgarg 2014年

Answers:


125

join()方法与threading或一起使用时multiprocessing不相关str.join()-它实际上并没有将任何内容串联在一起。相反,它仅表示“等待此[线程/进程]完成”。join之所以使用该名称,是因为该multiprocessing模块的API看起来类似于该threading模块的API,并且该threading模块join用于其Thread对象。join在许多编程语言中,使用该术语表示“等待线程完成”是很常见的,因此Python也采用了它。

现在,您看到有和没有调用都延迟20秒的原因join()是因为默认情况下,当主进程准备退出时,它将隐式调用join()所有正在运行的multiprocessing.Process实例。在multiprocessing文档中并未对此进行明确说明,但在“ 编程指南”部分中进行了提及:

还请记住,非守护进程将自动加入。

您可以通过设置覆盖此行为daemon上的标志ProcessTrue之前,要启动的过程:

p = Process(target=say_hello)
p.daemon = True
p.start()
# Both parent and child will exit here, since the main process has completed.

如果这样做,则子进程将在主进程完成后立即终止

守护程序

进程的守护程序标志,一个布尔值。必须在调用start()之前进行设置。

初始值是从创建过程继承的。

进程退出时,它将尝试终止其所有守护程序子进程。


6
我的理解p.daemon=True是,这是为了“启动运行后台进程,而不会阻止主程序退出”。但是,如果“守护程序进程在主程序退出之前自动终止”,它的用途是什么?
MikeiLL 2014年

8
@MikeiLL基本上只要您要在父进程运行期间在后台进行任何操作,但是在退出主程序之前无需进行任何清理。也许某个工作进程从套接字或硬件设备读取数据,并通过队列将该数据反馈给父级,或者出于某种目的在后台对其进行处理?通常,我会说使用daemonic子进程不是很安全,因为该进程将终止而不允许清理它可能拥有的任何开放资源。(续)。
dano 2014年

7
@MikeiLL更好的做法是在退出主要过程之前向孩子发出信号,要求其清理并离开。您可能会认为在父级退出时让守护进程继续运行是有道理的,但请记住,multiprocessingAPI旨在尽可能地模仿threadingAPI。恶魔的threading.Thread对象,一旦主线程退出终止,所以恶魔的multiprocesing.Process行为方式相同的对象。
dano 2014年

38

没有 join(),主进程可以在子进程之前完成。我不确定在什么情况下会导致僵尸。

主要目的 join()是确保子流程在主流程执行任何依赖于子流程的工作之前完成。

的词源join()是与的相反fork,后者是Unix系列操作系统中用于创建子进程的常用术语。单个过程“分叉”成多个,然后“连接”成一个。


2
join()之所以使用这个名称,join()是因为它是用来等待threading.Thread对象完成的,并且该multiprocessingAPI旨在尽可能地模仿该threadingAPI。
dano

您的第二个陈述解决了我在当前项目中正在处理的问题。
MikeiLL 2014年

我了解主线程在等待子进程完成的那部分,但是这不会破坏异步执行的目的吗?它不是应该独立完成执行(子任务或流程)吗?
Apurva Kunkulol

1
@ApurvaKunkulol取决于您的使用方式,但是join()在主线程需要子线程的工作结果的情况下需要使用它。例如,如果要渲染某些东西,并将最终图像的1/4分配给4个子过程中的每个子过程,并希望在完成后显示整个图像。
罗素·波罗戈夫

@RussellBorogove啊!我知道了。那么异步活动的含义在这里有点不同。它必须仅表示以下事实:子进程旨在与主线程同时执行其任务,而主线程也完成其工作,而不仅仅是闲着等待子进程。
Apurva Kunkulol

12

我不会详细解释join它的作用,但是这里是它的词源和直觉,这应该有助于您更轻松地记住它的含义。

这个想法是执行“ 分叉 ”到多个进程中,其中一个是主进程,其余是工人(或“奴隶”)。工作人员完成后,他们“加入”主服务器,以便可以恢复串行执行。

join方法使主进程等待工作人员加入。该方法最好称为“等待”,因为这是它在主服务器中引起的实际行为(这就是POSIX中所称的内容,尽管POSIX线程也称其为“ join”)。连接仅是由于线程正确协作而产生的,不是主服务器执行的操作

自1963年以来,名称“ fork”和“ join”已在多处理中使用此含义。


因此,以某种方式使用该词join可能先于使用它来指称串联,而不是相反。
MikeiLL 2014年

1
并置中的使用不太可能源自多处理中的使用。而是两种意思分别从该单词的普通英语意义衍生而来。
罗素·博罗戈夫

2

join()用于等待辅助进程退出。必须先致电close()terminate()使用join()

就像@罗素提到的 的那样,join就像fork的相反对象(子流程)。

要运行加入,您必须先运行close(),这将阻止所有其他任务提交到池中,并在所有任务完成后退出。或者,terminate()通过立即停止所有工作进程退出运行。

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" 当主(父)进程退出但子进程仍在运行并且完成后,它没有父进程可以将其退出状态返回时,这是可能的。


2

join()调用可确保在完成所有多处理过程之前不会调用代码的后续行。

例如,如果不使用join(),则restart_program()甚至在进程完成之前,也会调用以下代码,这类似于异步,不是我们想要的(您可以尝试):

num_processes = 5

for i in range(num_processes):
    p = multiprocessing.Process(target=calculate_stuff, args=(i,))
    p.start()
    processes.append(p)
for p in processes:
    p.join() # call to ensure subsequent line (e.g. restart_program) 
             # is not called until all processes finish

restart_program()

0

要等待进程完成其工作并退出,请使用join()方法。

注意终止进程后,必须对join()进程进行处理,以便让后台机制有时间更新对象的状态以反映终止。

这是一个很好的例子,帮助我理解了:这里

我个人注意到的一件事是,我的主要过程暂停了,直到孩子使用join()方法完成了过程,这首先挫败了我的使用multiprocessing.Process()意图。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.