Python multiprocessing模块的.join()方法到底在做什么?

159 投票
6 回答
152822 浏览
提问于 2025-04-18 17:50

我正在学习关于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()

我加了一些打印输出和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 Module of the Week提供了一个非常易读的模块解释;“要等待一个进程完成工作并退出,可以使用join()方法。”但看起来至少在OS X上,这个进程本来就会自己结束。

我也在想这个方法的名字。.join()方法是在连接什么吗?是把一个进程和它的结束连接起来吗?还是说它只是和Python的原生.join()方法同名?

6 个回答

3

join() 是用来等待工作进程结束的。在使用 join() 之前,必须先调用 close()terminate()

就像 @Russell 提到的,join 可以看作是 fork 的反面(fork 是用来创建子进程的)。

要让 join() 正常工作,你需要先运行 close(),这样可以防止再提交新的任务到进程池,等所有任务完成后就会退出。另一种选择是运行 terminate(),这会立即停止所有工作进程并退出。

"子进程会闲着不动而不结束,变成僵尸进程,你必须手动杀掉它" 这种情况发生在主进程(父进程)退出后,但子进程仍在运行,一旦完成,它就没有父进程可以返回它的退出状态了。

9

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

我不打算详细解释join的具体功能,但我可以告诉你它的来源和背后的直觉,这样你会更容易记住它的意思。

这个概念是,执行过程会“分叉”成多个进程,其中一个是进程,其他的都是工作进程(或者叫次要进程)。当这些工作进程完成任务后,它们会“加入”到进程中,这样就可以继续顺序执行了。

join()的作用是让进程等待一个工作进程加入它。这个方法其实可以叫“等待”,因为它的实际效果就是让主进程停下来等着(在POSIX中也是这样叫的,虽然POSIX线程也叫它“join”)。这种“加入”只是因为线程之间的合作得当,并不是进程自己主动去做的事情。

“fork”和“join”这两个名字在多进程的上下文中,自1963年以来就一直被用来表示这个意思。

50

如果没有 join(),主程序可能会在子程序完成之前就结束。我不太确定在什么情况下会导致“僵尸进程”的出现。

join() 的主要作用是确保子程序完成后,主程序才会进行任何依赖于子程序工作的操作。

join() 这个名字的意思是它和 fork 是相反的。fork 是在 Unix 系统中用来创建子程序的常用术语。一个进程会“分叉”成多个,然后再“合并”回一个。

170

join() 方法在使用 threadingmultiprocessing 时,并不是在拼接什么东西,它的意思是“等这个 [线程/进程] 完成”。之所以叫 join,是因为 multiprocessing 模块的接口设计得和 threading 模块很像,而 threading 模块里的 Thread 对象也用到了 join。在很多编程语言中,用 join 来表示“等待线程完成”是很常见的,所以 Python 也就采用了这个说法。

你会发现无论调用 join() 还是不调用,都会有 20 秒的延迟,这是因为默认情况下,当主进程准备退出时,它会自动对所有正在运行的 multiprocessing.Process 实例调用 join()。这个在 multiprocessing 的文档中没有说得很清楚,但在 编程指南 的部分提到过:

请记住,非守护进程会被自动加入。

你可以通过在启动进程之前将 Processdaemon 标志设置为 True 来改变这种行为:

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

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

daemon

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

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

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

撰写回答