我有一个python脚本:zombie.py
from multiprocessing import Process
from time import sleep
import atexit
def foo():
while True:
sleep(10)
@atexit.register
def stop_foo():
p.terminate()
p.join()
if __name__ == '__main__':
p = Process(target=foo)
p.start()
while True:
sleep(10)
当我用python zombie.py &
运行它并用kill -2
终止父进程时,stop()
被正确调用,并且两个进程都终止。
现在,假设我有一个bash脚本zombie.sh:
#!/bin/sh
python zombie.py &
echo "done"
我从命令行运行./zombie.sh
。
现在,当父母被杀时,stop()
永远不会被调用。如果在父进程上运行kill -2
,则不会发生任何事情。kill -15
或kill -9
都只是杀死父进程,而不是子进程:
[foo@bar ~]$ ./zombie.sh
done
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27220 1 0 17:57 pts/3 00:00:00 python zombie.py
foo 27221 27220 0 17:57 pts/3 00:00:00 python zombie.py
[foo@bar ~]$ kill -2 27220
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27220 1 0 17:57 pts/3 00:00:00 python zombie.py
foo 27221 27220 0 17:57 pts/3 00:00:00 python zombie.py
[foo@bar ~]$ kill 27220
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27221 1 0 17:57 pts/3 00:00:00 python zombie.py
这是怎么回事?如何确保子进程与父进程一起终止?
atexit
和p.daemon = True
都不会真正地确保子进程将与父进程一起消亡。接收SIGTERM不会触发atexit
例程。
为了确保孩子在父亲去世时被杀死,你必须在父亲身上安装一个信号处理器。通过这种方式,您可以对大多数信号(SIGQUIT、SIGINT、SIGHUP、SIGTERM…)做出反应,但不能对SIGKILL做出反应;根本没有办法对接收信号的过程中的信号做出反应
为所有有用的信号安装一个信号处理程序,并在该处理程序中终止子进程。
更新:此解决方案不适用于被信号杀死的进程。
您的子进程不是僵尸。它还活着。
如果希望子进程在其父进程正常退出时终止,则将p.daemon = True
设置在p.start()
之前。来自文档:
当进程退出时,它会尝试终止其所有守护进程子进程。
查看源代码,很明显,multiprocessing
使用atexit
回调来杀死其守护进程子进程,即如果父进程被信号杀死,它将不起作用。例如:
#!/usr/bin/env python
import logging
import os
import signal
import sys
from multiprocessing import Process, log_to_stderr
from threading import Timer
from time import sleep
def foo():
while True:
sleep(1)
if __name__ == '__main__':
log_to_stderr().setLevel(logging.DEBUG)
p = Process(target=foo)
p.daemon = True
p.start()
# either kill itself or exit normally in 5 seconds
if '--kill' in sys.argv:
Timer(5, os.kill, [os.getpid(), signal.SIGTERM]).start()
else: # exit normally
sleep(5)
输出
$python kill-orphan.py[INF/Process-1]调用self.run()的子进程[INF/MainProcess]进程关闭[DEBUG/MainProcess]运行优先级>=0的所有"atexit"终结器[INF/MainProcess]正在调用守护进程Process-1的terminate()[INF/MainProcess]正在为进程process-1调用join()[DEBUG/MainProcess]正在运行剩余的"atexit"终结器
注意"正在为守护进程调用terminate()">行。
输出(带--kill
)
$python kill-orphan.py--kill[INF/Process-1]调用self.run()的子进程
日志显示,如果父进程被信号杀死,则不会调用"atexit"回调(在这种情况下,ps
显示子进程还活着)。另请参阅多进程守护程序在父级退出时未终止。