Python multiprocessing模块的.join()方法到底在做什么?
我正在学习关于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 个回答
join()
是用来等待工作进程结束的。在使用 join()
之前,必须先调用 close()
或 terminate()
。
就像 @Russell 提到的,join 可以看作是 fork 的反面(fork 是用来创建子进程的)。
要让 join()
正常工作,你需要先运行 close()
,这样可以防止再提交新的任务到进程池,等所有任务完成后就会退出。另一种选择是运行 terminate()
,这会立即停止所有工作进程并退出。
"子进程会闲着不动而不结束,变成僵尸进程,你必须手动杀掉它"
这种情况发生在主进程(父进程)退出后,但子进程仍在运行,一旦完成,它就没有父进程可以返回它的退出状态了。
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()
我不打算详细解释join
的具体功能,但我可以告诉你它的来源和背后的直觉,这样你会更容易记住它的意思。
这个概念是,执行过程会“分叉”成多个进程,其中一个是主进程,其他的都是工作进程(或者叫次要进程)。当这些工作进程完成任务后,它们会“加入”到主进程中,这样就可以继续顺序执行了。
join()
的作用是让主进程等待一个工作进程加入它。这个方法其实可以叫“等待”,因为它的实际效果就是让主进程停下来等着(在POSIX中也是这样叫的,虽然POSIX线程也叫它“join”)。这种“加入”只是因为线程之间的合作得当,并不是主进程自己主动去做的事情。
“fork”和“join”这两个名字在多进程的上下文中,自1963年以来就一直被用来表示这个意思。
如果没有 join()
,主程序可能会在子程序完成之前就结束。我不太确定在什么情况下会导致“僵尸进程”的出现。
join()
的主要作用是确保子程序完成后,主程序才会进行任何依赖于子程序工作的操作。
join()
这个名字的意思是它和 fork
是相反的。fork
是在 Unix 系统中用来创建子程序的常用术语。一个进程会“分叉”成多个,然后再“合并”回一个。
join()
方法在使用 threading
或 multiprocessing
时,并不是在拼接什么东西,它的意思是“等这个 [线程/进程] 完成”。之所以叫 join
,是因为 multiprocessing
模块的接口设计得和 threading
模块很像,而 threading
模块里的 Thread
对象也用到了 join
。在很多编程语言中,用 join
来表示“等待线程完成”是很常见的,所以 Python 也就采用了这个说法。
你会发现无论调用 join()
还是不调用,都会有 20 秒的延迟,这是因为默认情况下,当主进程准备退出时,它会自动对所有正在运行的 multiprocessing.Process
实例调用 join()
。这个在 multiprocessing
的文档中没有说得很清楚,但在 编程指南 的部分提到过:
请记住,非守护进程会被自动加入。
你可以通过在启动进程之前将 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.
如果这样做,子进程 会在主进程完成后立即被终止:
daemon
进程的守护标志,一个布尔值。这个必须在调用
start()
之前设置。初始值是从创建进程继承的。
当一个进程退出时,它会尝试终止所有的守护子进程。