从bash调用脚本时终止子进程



我有一个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 -15kill -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

这是怎么回事?如何确保子进程与父进程一起终止?

atexitp.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显示子进程还活着)。另请参阅多进程守护程序在父级退出时未终止。

最新更新