为什么在Python中主进程退出时子进程(守护进程=True)不会退出?

3
这里是 Python 多进程中 "daemon" 标志的官方解释:

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

据我理解,当父进程退出时,将会杀死所有被设置为 True 的守护进程子进程。下面是我用来证明我的猜测的代码。但结果与我所想的不同。
import multiprocessing


def child():
    while True:
        pass


for x in xrange(1, 4):
    proc = multiprocessing.Process(target=child, args=())
    proc.daemon=True
    proc.start()


while True:
    pass

以上代码启动了4个子进程和一个主进程。 我终止了主进程,但这4个子进程没有退出。
那么为什么它们没有被主进程终止呢?因为它们的守护进程(daemon)选项被设置为true。

你用的是哪个操作系统?我在Windows上尝试时,出现了一堆与进程相关的错误。 - nj2237
我在我的Linux系统上运行了这个程序,并添加了一个答案,请查看。 - nj2237
2个回答

6

注意:

  • 使用xrange意味着使用Python 2

  • xrange(1, 4)将产生3个值而不是4个(因此,只会有3个子项)

事实并非如此。文档([Python 2.Docs]: multiprocessing - daemon)可能需要更具体的说明。

问题在于,当退出时,multiprocessing会注册一个清理函数来终止所有守护进程子项。这是通过[Python 2.Docs]: atexit - Exit handlers完成的(强调是我的):

注意:通过此模块注册的函数在程序被Python未处理的信号杀死、检测到Python致命内部错误或调用os._exit()时不会被调用。
您没有处理TERM信号(默认由kill命令发送),因此清理函数不会被主进程调用(导致其子进程继续运行)。
我修改了您的代码以更好地说明这种行为。

code00.py:

#!/usr/bin/env python2

import multiprocessing
import os
import sys
import time


print_text_pattern = "Output from process {:s} - pid: {:d}, ppid: {:d}"


def child(name):
    while True:
        print(print_text_pattern.format(name, os.getpid(), os.getppid()))
        time.sleep(1)


def main(*argv):
    procs = list()
    for x in xrange(1, 3):
        proc_name = "Child{:d}".format(x)
        proc = multiprocessing.Process(target=child, args=(proc_name,))
        proc.daemon = True #x % 2 == 0
        print("Process {:s} daemon: {:}".format(proc_name, proc.daemon))
        procs.append(proc)

    for proc in procs:
        proc.start()

    counter = 0
    while counter < 3:
        print(print_text_pattern.format("Main", os.getpid(), os.getppid()))
        time.sleep(1)
        counter += 1


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

笔记

  • 稍微改变了子进程的生成方式:所有子进程都是在第一次创建后才开始运行。

  • 为每个进程添加了一些打印调用,以跟踪它们在标准输出中的活动 - 还添加了一些时间延迟调用(1秒),以避免产生过多的输出。

  • 最重要的是 - 主进程不再永远运行。在某个时刻,它会优雅地退出(经过3个循环 - 根据计数器变量),然后就会出现我之前提到的行为。
    这也可以通过拦截TERM信号(和其他可以由kill命令显式发送的信号)并进行清理来实现 - 这样当杀死主进程时,子进程也会被杀死 - 但这更加复杂。

  • 我简化了一些东西,只生成了2个子进程。

  • 将所有内容移动到一个main函数中(为了结构),并放在一个if __name__ == "__main__":条件语句中,这样如果你导入该模块,就不会生成进程。

  • 为每个子进程的proc.daemon赋予不同的值,然后监视输出和ps -ef | grep "code00.py"输出。

  • child函数添加了一个参数(name),但这只是为了显示目的。

输出:

[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow]> python2 ./code00.py
Python 2.7.12 (default, Oct  8 2019, 14:14:10) [GCC 5.4.0 20160609] 064bit on linux2

Process Child1 daemon: True
Process Child2 daemon: True
Output from process Main - pid: 1433, ppid: 1209
Output from process Child1 - pid: 1434, ppid: 1433
Output from process Child2 - pid: 1435, ppid: 1433
Output from process Main - pid: 1433, ppid: 1209
Output from process Child2 - pid: 1435, ppid: 1433
Output from process Child1 - pid: 1434, ppid: 1433
Output from process Main - pid: 1433, ppid: 1209
Output from process Child1 - pid: 1434, ppid: 1433
Output from process Child2 - pid: 1435, ppid: 1433
Output from process Child1 - pid: 1434, ppid: 1433
Output from process Child2 - pid: 1435, ppid: 1433

Done.

讲解得非常清楚!赞一个 :) 所以简而言之,你的意思是当主进程无限期地运行直到被突然终止时,OP没有处理TERM信号?这就是为什么子进程的清理函数没有被调用的原因? - nj2237
@nj2237:谢谢。是的,如果OP能够处理TERM信号,并在处理程序内调用multiprocessing注册的清理函数,那么问题中的代码将会按预期运行。 - CristiFati
好的,谢谢澄清。因此在主进程中调用 sys.exit() 也处理了 TERM 信号,这就是为什么它对我有效的原因。 - nj2237
@nj2237 我并不是这么说的。sys.exit 会优雅地退出进程,并且 atexit 函数也会被执行。我不确定它是否发送信号(我没有深入研究过代码)。 - CristiFati
好的,我会编辑我的回答。另外我检查了这个链接 - https://pymotw.com/2/atexit/index.html,看起来调用 sys.exit 也会注册 atexit 回调函数。 - nj2237

0

是的,你的理解是正确的,你用来测试的代码也可以工作。

我只是添加了一些睡眠语句来调试输出(如果没有 sleep,从大量的打印输出中推断出结果是很困难的):

import multiprocessing
import time
import sys

print("main")

def child():
    while True:
        print("child")
        time.sleep(3)

for x in xrange(1, 4):
    proc = multiprocessing.Process(target=child, args=())
    proc.daemon=True
    proc.start()

time.sleep(7)
print("exit")
sys.exit() # this exits the main process

现在,当我运行此脚本并且正在运行时,我执行了ps aux,并可以看到来自此脚本的四个进程正在运行。 7秒后,当我再次执行ps aux时,我就不能再看到这些进程在运行了 - 这意味着:

当主进程退出时,它终止了所有守护进程子进程。

之后,我还将proc.daemon设置为False,并再次运行了脚本。 这一次,即使在7秒后,当我执行ps aux时,我仍然可以看到子进程在运行(因为它们现在是非守护进程,即使主进程终止后也不会退出)。
所以这个脚本按预期工作 - 如果您仍然有问题,请告诉我。 编辑1: 感谢@CristiFati指出原始清理问题。 这段代码有效是因为调用sys.exit()也注册了atexit回调,如here所述。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接