为什么在 python 中主进程退出时子进程 (daemon=True) 不退出?
Why child process (daemon=True) not exiting when main process exit in python?
这里是python多处理中daemon
标志的官方解释:
When a process exits, it attempts to terminate all of its daemonic child processes.
据我了解,父进程在退出时会杀死其守护进程标志设置为 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个子进程和1个主进程。
我杀了主进程但是4个子进程没有退出
既然守护进程设置为 true,为什么它们没有被 main 终止?
是的,您的理解是正确的,您的测试代码也有效。
我只是添加了一些睡眠语句来调试输出(没有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
时,我再也看不到那些进程 运行 - 这意味着:
When the main process exited, it terminated all of its daemonic child processes.
之后,我也将 proc.daemon
设置为 False
,并再次设置 运行 脚本。这次,即使在 7 秒后,当我执行 ps aux
时,我仍然可以看到子进程 运行(因为它们现在是 non-daemonic,即使在主进程终止后它们也不会退出).
所以这按预期工作 - 如果您在这里仍有问题,请告诉我。
编辑 1:
感谢@CristiFati 指出最初的清理问题。
这段代码之所以有效,是因为调用 sys.exit()
还注册了 atexit
回调,详见 here.
备注:
- 使用xrange意味着Python 2
xrange(1, 4)
将产生 3 个值而不是 4 个(因此,只有 3 个 children)
事情并非如此。文档 ([Python 2.Docs]: multiprocessing - daemon) 可能应该更具体。
事情是 multiprocessing 注册了一个 cleanup 函数 来杀死它所有的 deamonic children 退出时。这是通过 [Python 2.Docs]: atexit - Exit handlers:
完成的
Note: The functions registered via this module are not called when the program is killed by a signal not handled by Python, when a Python fatal internal error is detected, or when os._exit() is called.
你没有处理TERM信号(默认由kill命令发送),因此不会调用清理函数由主进程(离开它的 children 运行)。
我修改了您的代码以更好地说明行为。
code00.py:
#!/usr/bin/env python2
import sys
import multiprocessing
import os
import time
print_text_pattern = "Output from process {0:s} - pid: {1:d}, ppid: {2:d}"
def child(name):
while True:
print(print_text_pattern.format(name, os.getpid(), os.getppid()))
time.sleep(1)
def main():
procs = list()
for x in xrange(1, 3):
proc_name = "Child{0:d}".format(x)
proc = multiprocessing.Process(target=child, args=(proc_name,))
proc.daemon = True #x % 2 == 0
print("Process {0:s} daemon: {1:}".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 {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main()
print("\nDone.")
备注:
- 稍微改变了 children 进程的生成方式:所有进程都是在 1st 时创建的,然后才启动
- 添加了一些来自每个进程的 print 调用,以在 stdout 中跟踪它们的 activity - 还添加了一些
time.sleep
调用(1 秒),以避免产生过多的输出
- 最重要 - 主进程不再永远运行。在某些时候它会优雅地退出(在 3 个周期后 - 由于 counter 变量),并且当我之前提到的行为开始时。
这也可能是可能的通过拦截 TERM 信号(以及其他可以由 kill 命令显式发送的信号)然后执行清理 - 以这种方式 children 在杀死主进程时也会被杀死 - 但那更复杂
- 我稍微简化了一些,所以只生成了 2 children
- 移动了 main 函数(用于结构)中包含在
if __name__ == "__main__":
条件中的所有内容,因此如果您 import 模块
- 为每个 child 提供不同的值
proc.daemon
然后监视输出和 ps -ef | grep "code00.py"
输出
- 向 child 函数添加了一个参数 (name),但这仅用于显示目的
输出:
[cfati@cfati-ubtu16x64-0:~/Work/Dev/Whosebug]> python2 code00.py
Python 2.7.12 (default, Oct 8 2019, 14:14:10) [GCC 5.4.0 20160609] 64bit 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.
这里是python多处理中daemon
标志的官方解释:
When a process exits, it attempts to terminate all of its daemonic child processes.
据我了解,父进程在退出时会杀死其守护进程标志设置为 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个子进程和1个主进程。 我杀了主进程但是4个子进程没有退出
既然守护进程设置为 true,为什么它们没有被 main 终止?
是的,您的理解是正确的,您的测试代码也有效。
我只是添加了一些睡眠语句来调试输出(没有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
时,我再也看不到那些进程 运行 - 这意味着:
When the main process exited, it terminated all of its daemonic child processes.
之后,我也将 proc.daemon
设置为 False
,并再次设置 运行 脚本。这次,即使在 7 秒后,当我执行 ps aux
时,我仍然可以看到子进程 运行(因为它们现在是 non-daemonic,即使在主进程终止后它们也不会退出).
所以这按预期工作 - 如果您在这里仍有问题,请告诉我。
编辑 1:
感谢@CristiFati 指出最初的清理问题。
这段代码之所以有效,是因为调用 sys.exit()
还注册了 atexit
回调,详见 here.
备注:
- 使用xrange意味着Python 2
xrange(1, 4)
将产生 3 个值而不是 4 个(因此,只有 3 个 children)
事情并非如此。文档 ([Python 2.Docs]: multiprocessing - daemon) 可能应该更具体。
事情是 multiprocessing 注册了一个 cleanup 函数 来杀死它所有的 deamonic children 退出时。这是通过 [Python 2.Docs]: atexit - Exit handlers:
完成的Note: The functions registered via this module are not called when the program is killed by a signal not handled by Python, when a Python fatal internal error is detected, or when os._exit() is called.
你没有处理TERM信号(默认由kill命令发送),因此不会调用清理函数由主进程(离开它的 children 运行)。
我修改了您的代码以更好地说明行为。
code00.py:
#!/usr/bin/env python2
import sys
import multiprocessing
import os
import time
print_text_pattern = "Output from process {0:s} - pid: {1:d}, ppid: {2:d}"
def child(name):
while True:
print(print_text_pattern.format(name, os.getpid(), os.getppid()))
time.sleep(1)
def main():
procs = list()
for x in xrange(1, 3):
proc_name = "Child{0:d}".format(x)
proc = multiprocessing.Process(target=child, args=(proc_name,))
proc.daemon = True #x % 2 == 0
print("Process {0:s} daemon: {1:}".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 {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main()
print("\nDone.")
备注:
- 稍微改变了 children 进程的生成方式:所有进程都是在 1st 时创建的,然后才启动
- 添加了一些来自每个进程的 print 调用,以在 stdout 中跟踪它们的 activity - 还添加了一些
time.sleep
调用(1 秒),以避免产生过多的输出 - 最重要 - 主进程不再永远运行。在某些时候它会优雅地退出(在 3 个周期后 - 由于 counter 变量),并且当我之前提到的行为开始时。
这也可能是可能的通过拦截 TERM 信号(以及其他可以由 kill 命令显式发送的信号)然后执行清理 - 以这种方式 children 在杀死主进程时也会被杀死 - 但那更复杂 - 我稍微简化了一些,所以只生成了 2 children
- 移动了 main 函数(用于结构)中包含在
if __name__ == "__main__":
条件中的所有内容,因此如果您 import 模块 - 为每个 child 提供不同的值
proc.daemon
然后监视输出和ps -ef | grep "code00.py"
输出 - 向 child 函数添加了一个参数 (name),但这仅用于显示目的
输出:
[cfati@cfati-ubtu16x64-0:~/Work/Dev/Whosebug]> python2 code00.py Python 2.7.12 (default, Oct 8 2019, 14:14:10) [GCC 5.4.0 20160609] 64bit 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.