【问题标题】:Python multiprocessing: where should join() be called, if process' children have grandchildren?Python多处理:如果进程的孩子有孙子,应该在哪里调用join()?
【发布时间】:2014-05-17 23:42:27
【问题描述】:

这是我正在处理的一个玩具问题:

import multiprocessing as mp

def task2():
    print "I am doing something important."

def task1():
    listOfProcesses = []
    for i in range(5):
        process = mp.Process(target=task2)
        process.start()
        listOfProcesses.append(process)

def task0():
    listOfProcesses = []
    for i in range(5):
        process = mp.Process(target=task1)
        process.start()
        listOfProcesses.append(process)

if __name__ == '__main__':
    task0()

现在,我无法理解在这种情况下应该在哪里调用 join。

如果我像这样更改task0 的定义:

def task0():
    listOfProcesses = []

    for i in range(5):
        process = mp.Process(target=task2)
        process.start()
        listOfProcesses.append(process)

    for process in listOfProcesses:
        process.join()

然后一切似乎都正常工作,但我不明白我在这里真正在做什么。 task1 只启动它的孩子,它不加入他们。那么加入task0task1 意味着什么?

【问题讨论】:

  • @roippi 我打错字了,我已经修正了它并添加了一些内容以使我调用 task0 的位置清晰。

标签: python python-2.7 multiprocessing


【解决方案1】:

join 在概念上相当简单 - x.join 表示“在 x 终止之前,当前的执行线程(即进程)无法继续超过 this 点。”

因此,一般来说,您不希望您的 ma​​in 线程在某个时间点之后继续进行,直到您的所有工作人员都完成了他们的工作。由于您在主线程中执行task0,因此执行join 会阻止您的主线程继续执行该点,直到您的所有工作人员(task1 task2)都完成。

但是等等,我没有在task1join

没错。但是task1 的进程在其所有task2 完成之前仍然不会终止。这与process groups 的POSIX 概念有关——父进程在其所有子进程终止之前不会终止。那么,让我们看看这个简化示例的输出:

import multiprocessing as mp
from time import sleep

def task2():
    sleep(1)
    print "I am doing something important."

def task1():
    for i in range(2):
        process = mp.Process(target=task2)
        process.start()

    print 'task1 done'

def task0():
    process = mp.Process(target=task1)
    process.start()
    process.join()

if __name__ == '__main__':
    task0()
    print 'all done'

输出:

task1 done
I am doing something important.
I am doing something important.
all done

如您所见,task1 已结束,但直到其子进程结束时才终止 - 这意味着 task0 中的 join 块正确阻止了我们的主线程终止,直到所有工作线程都终止。

为了好玩,这里是运行原始脚本时ps jf 的输出,没有joins,唯一的修改time.sleep 被扔进task2,所以我可以捕获它运行:

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 6780  7385  7385  7385 pts/11    7677 Ss    1000   0:00 bash
 7385  7677  7677  7385 pts/11    7677 R+    1000   0:00  \_ ps jf
 6780  6866  6866  6866 pts/7     7646 Ss    1000   0:00 bash
 6866  7646  7646  6866 pts/7     7646 S+    1000   0:00  \_ python test
 7646  7647  7646  6866 pts/7     7646 S+    1000   0:00      \_ python test
 7647  7672  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7647  7673  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7647  7674  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7647  7675  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7647  7676  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7646  7648  7646  6866 pts/7     7646 S+    1000   0:00      \_ python test
 7648  7665  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7648  7666  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7648  7667  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7648  7668  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7648  7669  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7646  7649  7646  6866 pts/7     7646 S+    1000   0:00      \_ python test
 7649  7656  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7649  7657  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7649  7658  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7649  7659  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7649  7660  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7646  7650  7646  6866 pts/7     7646 S+    1000   0:00      \_ python test
 7650  7652  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7650  7653  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7650  7654  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7650  7655  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7650  7670  7646  6866 pts/7     7646 S+    1000   0:00      |   \_ python test
 7646  7651  7646  6866 pts/7     7646 S+    1000   0:00      \_ python test
 7651  7661  7646  6866 pts/7     7646 S+    1000   0:00          \_ python test
 7651  7662  7646  6866 pts/7     7646 S+    1000   0:00          \_ python test
 7651  7663  7646  6866 pts/7     7646 S+    1000   0:00          \_ python test
 7651  7664  7646  6866 pts/7     7646 S+    1000   0:00          \_ python test
 7651  7671  7646  6866 pts/7     7646 S+    1000   0:00          \_ python test

您可以看到我们的主进程(执行task0 的那个)和“第一个孩子”(执行task1 的那个)仍然存在,即使它们显然没有要执行的python 代码。它们也是同一个进程组 (TPGID) 的所有成员。

总结一下,伙计

所有这些都是一种冗长的说法:主线程中的join 通常是你所需要的,因为你可以保证任何子进程都会等待他们的子进程在自己终止之前终止。

【讨论】:

    【解决方案2】:

    在类 Unix 系统(Linux、BSD 等)上,mp.Process 实际上调用os.fork,而生成的进程对象的join 方法调用wait(或变体)1 等待它(即等待那个特定的进程,而不仅仅是任何任意进程)。

    fork 的子代只能被其父代wait-ed,2 所以task0 可以等待每个task1,但不能等待任何task1task2s。同时,每个task1 可以等待所有自己的task2s,但不能等待任何其他task1task2s。

    因为每个task2 都很短(并且每个进程在从其target= 函数返回时退出),所以无论您是否明确join,在这里都很难看出任何区别。您需要做一些更慢的事情(例如,time.sleep() 或做一些实际的工作)才能看到任何真正的差异。


    1实际调用的是os.waitpid();见multiprocessing/forking.py。实际调用在poll 函数中。

    2如果父进程在没有等待其子进程的情况下退出,这些子进程将“孤立”并作为代理父进程传递给 PID 1 (init)。进程 1 循环调用 wait(或等效项)来清理它们。


    (Windows 变体使用不同的调用——例如,它不能 fork——而且我不在 Windows 上工作,所以我不确定那里的实际行为如何。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-22
      • 2012-12-12
      • 2012-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-01
      • 1970-01-01
      相关资源
      最近更新 更多