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

Posted

技术标签:

【中文标题】Python 多处理模块的 .join() 方法到底在做啥?【英文标题】:What exactly is Python multiprocessing Module's .join() Method Doing?Python 多处理模块的 .join() 方法到底在做什么? 【发布时间】:2014-10-13 00:03:55 【问题描述】:

了解 Python Multiprocessing(来自 PMOTW article)并希望了解 join() 方法的具体作用。

在old tutorial from 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()

我添加了PIDtime.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 模块提供 very readable explanation of the module; “要等到进程完成其工作并退出,请使用 join() 方法。”,但似乎至少 OS X 无论如何都在这样做。

我也想知道方法的名称。 .join() 方法在这里连接任何东西吗?它是否将一个过程与它的结束连接起来?还是它只是与 Python 的原生 .join() 方法共享一个名称?

【问题讨论】:

据我所知,它持有主线程并等待子进程完成然后重新加入主线程中的资源,主要是干净退出。 嗯,有道理。所以实际的CPU, Memory resources 正在与父进程分离,然后join 在子进程完成后再次返回? 是的,它就是这么做的。所以,如果你不加入他们,当子进程完成时,它只是作为一个不存在或死的进程而存在 @abhishekgarg 这不是真的。当主进程完成时,子进程将被隐式加入。 @dano,我也在学习 python,我只是分享了我在测试中发现的内容,在我的测试中,我有一个永无止境的主进程,所以也许这就是为什么我认为这些子进程已经失效。 【参考方案1】:

我不打算详细解释join 的作用,但这里是它的词源和直觉,它应该可以帮助你更容易地记住它的含义。

想法是将“forks”执行到多个进程中,其中一个是 main/primary 进程,其余的 worker(或 minor/次要)。当工作人员完成后,他们“加入”进程,以便可以恢复串行执行。

join() 导致 main 进程等待工作人员加入。该方法最好称为“等待”,因为这是它在主服务器中引起的实际行为(这就是它在 POSIX 中的名称,尽管 POSIX 线程也将其称为“加入”)。加入仅作为线程正常协作的效果而发生,这不是 main 进程 执行 的事情。

名称“fork”和“join”在多处理since 1963中具有此含义。

【讨论】:

所以在某种程度上,join 这个词的使用可能先于它用于指代连接,而不是相反。 连接中的使用不太可能源自多处理中的使用;相反,这两种含义都与该词的普通英语含义分开。【参考方案2】:

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

注意在终止进程后加入() 进程很重要,以便给后台机器时间来更新对象的状态以反映终止。

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

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

【讨论】:

【参考方案3】:

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()

【讨论】:

【参考方案4】:

join() 用于等待工作进程退出。在使用join() 之前,必须先致电close()terminate()

就像@Russell 提到的那样,joinfork (产生子进程)相反。

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

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

【讨论】:

【参考方案5】:

join() 方法与threadingmultiprocessing 一起使用时,与str.join() 无关——它实际上并没有将任何东西连接在一起。相反,它只是意味着“等待这个[线程/进程]完成”。使用名称join 是因为multiprocessing 模块的API 看起来与threading 模块的API 相似,而threading 模块使用join 作为其Thread 对象。使用术语 join 来表示“等待线程完成”在许多编程语言中都很常见,因此 Python 也采用了它。

现在,无论是否调用join(),您都会看到 20 秒延迟的原因是因为默认情况下,当主进程准备退出时,它会在所有正在运行的 multiprocessing.Process 上隐式调用 join()实例。这在multiprocessing 文档中没有像应有的那样明确说明,但在Programming Guidelines 部分中提到了:

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

您可以通过在启动进程之前将Process 上的daemon 标志设置为True 来覆盖此行为:

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

如果你这样做,子进程will be terminated as soon as the main process completes:

守护进程

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

初始值继承自创建过程。

当一个进程退出时,它会尝试终止它的所有守护进程 子进程。

【讨论】:

我理解p.daemon=True 是为了“启动一个后台进程,该进程在不阻止主程序退出的情况下运行”。但是如果“主程序退出前守护进程自动终止”,它到底有什么用呢? @MikeiLL 只要父进程正在运行,基本上你想要在后台进行的任何事情,但在退出主程序之前不需要优雅地清理。也许一个工作进程从套接字或硬件设备读取数据,并通过队列将该数据反馈给父进程,或者出于某种目的在后台处理它?一般来说,我会说使用daemonic 子进程不是很安全,因为该进程将被终止而不允许清理它可能拥有的任何开放资源..(续)。 @MikeiLL 更好的做法是在退出主进程之前通知孩子清理并退出。您可能认为在父进程退出时让守护子进程继续运行是有意义的,但请记住,multiprocessing API 旨在尽可能地模仿threading API。守护进程threading.Thread 对象在主线程退出后立即终止,因此守护进程multiprocesing.Process 对象的行为方式相同。【参考方案6】:

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

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

join() 的词源与fork 正好相反,fork 是 Unix 系列操作系统中用于创建子进程的常用术语。一个进程“分叉”成几个,然后“加入”回一个。

【讨论】:

它使用名称join(),因为join() 用于等待threading.Thread 对象完成,而multiprocessing API 旨在尽可能模仿threading API尽可能。 您的第二个陈述解决了我在当前项目中处理的问题。 我理解主线程等待子进程完成的部分,但这不是违背异步执行的目的吗?它不应该独立完成执行(子任务或流程)吗? @ApurvaKunkulol 取决于您如何使用它,但在主线程需要子线程工作结果的情况下需要join()。例如,如果您正在渲染某些内容并将最终图像的 1/4 分配给 4 个子进程中的每一个,并希望在完成后显示整个图像。 @RussellBorogove 啊!我知道了。那么这里异步活动的含义就有点不同了。它必须仅意味着子进程旨在与主线程同时执行它们的任务,而主线程也完成它的工作,而不是只是空闲地等待子进程。

以上是关于Python 多处理模块的 .join() 方法到底在做啥?的主要内容,如果未能解决你的问题,请参考以下文章

在队列为空之前调用join时的Python 3多处理队列死锁

Python threading 中join()的作用

python多线程之 threading模块详解

Python程序中的进程操作-开启多进程(multiprocess.process)

再看python多线程------threading模块

Python多线程线程在继续之前不等待.join()