确保子进程在退出 Python 程序时死亡
Posted
技术标签:
【中文标题】确保子进程在退出 Python 程序时死亡【英文标题】:Ensuring subprocesses are dead on exiting Python program 【发布时间】:2010-09-24 03:06:31 【问题描述】:有没有办法确保所有创建的子进程在 Python 程序退出时都死了?我所说的子进程是指那些使用 subprocess.Popen() 创建的。
如果不是,我应该遍历所有发出的杀戮然后杀掉 -9 吗?有什么清洁的吗?
【问题讨论】:
相关:How to terminate a python subprocess launched with shell=True 相关:Python: how to kill child process(es) when parent dies? 【参考方案1】:您可以为此使用atexit,并注册任何要在程序退出时运行的清理任务。
atexit.register(func[, *args[, **kargs]])
在您的清理过程中,您还可以实现自己的等待,并在您希望的超时发生时将其终止。
>>> import atexit
>>> import sys
>>> import time
>>>
>>>
>>>
>>> def cleanup():
... timeout_sec = 5
... for p in all_processes: # list of your processes
... p_sec = 0
... for second in range(timeout_sec):
... if p.poll() == None:
... time.sleep(1)
... p_sec += 1
... if p_sec >= timeout_sec:
... p.kill() # supported from python 2.6
... print 'cleaned up!'
...
>>>
>>> atexit.register(cleanup)
>>>
>>> sys.exit()
cleaned up!
注意 -- 如果该进程(父进程)被杀死,注册的函数将不会运行。
python >= 2.6 不再需要以下windows方法
这是一种在 Windows 中终止进程的方法。您的 Popen 对象具有 pid 属性,因此您可以通过 success = win_kill(p.pid) 调用它(需要安装 pywin32):
def win_kill(pid):
'''kill a process by specified PID in windows'''
import win32api
import win32con
hProc = None
try:
hProc = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, pid)
win32api.TerminateProcess(hProc, 0)
except Exception:
return False
finally:
if hProc != None:
hProc.Close()
return True
【讨论】:
你能解释一下你在windows代码中做了什么吗? 既然 p.kill() 存在,为什么需要“win_kill”?它适用于 2.6 之前的 python 用户吗? 是的,我相信当时 2.5 仍在广泛使用,并且 p.kill() 在 windows 中不可用。 这个答案可以简化为 Python 3.3+,其中Popen.wait()
接受 timeout
参数。首先循环调用子进程上的p.teminate()
(请求正常关闭)。然后在设置了超时时间的每个进程上调用wait()
,如果抛出超时过期异常,则调用p.kill()
。【参考方案2】:
在 *nix 上,也许使用进程组可以帮助您 - 您也可以捕获由子进程生成的子进程。
if __name__ == "__main__":
os.setpgrp() # create new process group, become its leader
try:
# some code
finally:
os.killpg(0, signal.SIGKILL) # kill all processes in my group
另一个考虑是升级信号:从 SIGTERM(kill
的默认信号)到 SIGKILL(又名kill -9
)。在信号之间稍等片刻,让进程有机会在kill -9
它之前彻底退出。
【讨论】:
看起来这个策略在进程组中包含了父进程。因此,当您将 SIGTERM 发送到进程组时,父进程也会得到它。这可能是不可取的(因为它在我的流程中)。【参考方案3】:subprocess.Popen.wait()
是确保他们已经死亡的唯一方法。事实上,POSIX 操作系统要求您等待您的孩子。许多 *nix 会创建一个“僵尸”进程:父母没有等待的死去的孩子。
如果孩子写得相当好,它就会终止。通常,孩子们会从 PIPE 中阅读。关闭输入对孩子来说是一个很大的提示,它应该关闭商店并退出。
如果孩子有错误并且没有终止,您可能必须杀死它。你应该修复这个错误。
如果孩子是一个“永远服务”循环,并且没有设计为终止,您应该杀死它或提供一些输入或消息来强制它终止。
编辑。
在标准操作系统中,您拥有os.kill( PID, 9 )
。顺便说一句,杀死 -9 很苛刻。如果您可以使用 SIGABRT (6?) 或 SIGTERM (15) 杀死它们,那就更有礼貌了。
在 Windows 操作系统中,您没有可用的 os.kill
。查看 ActiveState Recipe 以终止 Windows 中的进程。
我们有 WSGI 服务器的子进程。为了终止它们,我们在一个特殊的 URL 上执行 GET;这会导致孩子清理并退出。
【讨论】:
【参考方案4】:寻找linux的解决方案(无需安装prctl):
def _set_pdeathsig(sig=signal.SIGTERM):
"""help function to ensure once parent process exits, its childrent processes will automatically die
"""
def callable():
libc = ctypes.CDLL("libc.so.6")
return libc.prctl(1, sig)
return callable
subprocess.Popen(your_command, preexec_fn=_set_pdeathsig(signal.SIGTERM))
【讨论】:
【参考方案5】:警告:仅限 Linux!您可以让您的孩子在父母去世时收到信号。
首先安装 python-prctl==1.5.0 然后更改您的父代码以启动您的子进程,如下所示
subprocess.Popen(["sleep", "100"], preexec_fn=lambda: prctl.set_pdeathsig(signal.SIGKILL))
这句话是:
启动子进程:sleep 100 在分叉之后和子进程执行之前,子进程注册“向我发送 SIGKILL 当我的父母终止时”。【讨论】:
另一个选项是pyprctl
,它不编译C 扩展(不需要编译器和libcap-dev
)并且更开放地获得许可。以同样的方式工作。【参考方案6】:
我需要对这个问题进行一些小改动(清理子进程,但不退出 Python 程序本身),因为这里没有在其他答案中提到它:
p=subprocess.Popen(your_command, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), 15)
setsid
将在新会话中运行程序,从而为它及其子进程分配一个新进程组。因此调用os.killpg
也不会关闭你自己的python 进程。
【讨论】:
不,你不能......这将改变进程本身的会话,如果你所追求的只是杀死孩子,那不是你想要的 我读过这个问题,你读过我的答案吗?我明确写道,我需要对这个问题进行一些小改动……如果您在 Google 上搜索“子进程退出子项”,这将是您找到的第一个结果。事实上,在不退出的情况下杀死孩子是一个比在退出时简单地杀死孩子更普遍的问题,因此它对其他遇到同样问题的人很有用。 *** 常见问题解答说:What, specifically, is the question asking for? Make sure your answer provides that – or a viable alternative.
我提供了一个替代和有用的答案,与此同时,您对我发布的两个答案都投了反对票。如果您停止骚扰那些试图为 *** 用户提供帮助的人,我相信整个社区都会欢迎。【参考方案7】:
orip 的回答很有帮助,但缺点是它会杀死您的进程并返回您的父级错误代码。我避免这样:
class CleanChildProcesses:
def __enter__(self):
os.setpgrp() # create new process group, become its leader
def __exit__(self, type, value, traceback):
try:
os.killpg(0, signal.SIGINT) # kill all processes in my group
except KeyboardInterrupt:
# SIGINT is delievered to this process as well as the child processes.
# Ignore it so that the existing exception, if any, is returned. This
# leaves us with a clean exit code if there was no exception.
pass
然后:
with CleanChildProcesses():
# Do your work here
当然你可以用 try/except/finally 来做到这一点,但是你必须分别处理异常和非异常情况。
【讨论】:
这不会禁用 control-c 吗?【参考方案8】:投票()
检查子进程是否已终止。 返回返回码属性。
【讨论】:
【参考方案9】:Windows 的解决方案可能是使用 win32 作业 api,例如How do I automatically destroy child processes in Windows?
这是一个现有的 python 实现
https://gist.github.com/ubershmekel/119697afba2eaecc6330
【讨论】:
【参考方案10】:有没有办法确保所有创建的子进程在 Python 程序退出时都死了?我所说的子进程是指那些使用 subprocess.Popen() 创建的。
您可能会违反封装并测试所有 Popen 进程都已通过这样做而终止
subprocess._cleanup()
print subprocess._active == []
如果不是,我应该遍历所有发出的杀戮然后杀掉 -9 吗?有什么清洁的吗?
如果不出去杀死所有幸存者,就无法确保所有子进程都死了。但是如果你有这个问题,那很可能是因为你有更深层次的设计问题。
【讨论】:
【参考方案11】:我实际上需要这样做,但它涉及运行远程命令。我们希望能够通过关闭与服务器的连接来停止进程。另外,例如,如果您在 python repl 中运行,如果您希望能够使用 Ctrl-C 退出,则可以选择作为前台运行。
import os, signal, time
class CleanChildProcesses:
"""
with CleanChildProcesses():
Do work here
"""
def __init__(self, time_to_die=5, foreground=False):
self.time_to_die = time_to_die # how long to give children to die before SIGKILL
self.foreground = foreground # If user wants to receive Ctrl-C
self.is_foreground = False
self.SIGNALS = (signal.SIGHUP, signal.SIGTERM, signal.SIGABRT, signal.SIGALRM, signal.SIGPIPE)
self.is_stopped = True # only call stop once (catch signal xor exiting 'with')
def _run_as_foreground(self):
if not self.foreground:
return False
try:
fd = os.open(os.ctermid(), os.O_RDWR)
except OSError:
# Happens if process not run from terminal (tty, pty)
return False
os.close(fd)
return True
def _signal_hdlr(self, sig, framte):
self.__exit__(None, None, None)
def start(self):
self.is_stopped = False
"""
When running out of remote shell, SIGHUP is only sent to the session
leader normally, the remote shell, so we need to make sure we are sent
SIGHUP. This also allows us not to kill ourselves with SIGKILL.
- A process group is called orphaned when the parent of every member is
either in the process group or outside the session. In particular,
the process group of the session leader is always orphaned.
- If termination of a process causes a process group to become orphaned,
and some member is stopped, then all are sent first SIGHUP and then
SIGCONT.
consider: prctl.set_pdeathsig(signal.SIGTERM)
"""
self.childpid = os.fork() # return 0 in the child branch, and the childpid in the parent branch
if self.childpid == 0:
try:
os.setpgrp() # create new process group, become its leader
os.kill(os.getpid(), signal.SIGSTOP) # child fork stops itself
finally:
os._exit(0) # shut down without going to __exit__
os.waitpid(self.childpid, os.WUNTRACED) # wait until child stopped after it created the process group
os.setpgid(0, self.childpid) # join child's group
if self._run_as_foreground():
hdlr = signal.signal(signal.SIGTTOU, signal.SIG_IGN) # ignore since would cause this process to stop
self.controlling_terminal = os.open(os.ctermid(), os.O_RDWR)
self.orig_fore_pg = os.tcgetpgrp(self.controlling_terminal) # sends SIGTTOU to this process
os.tcsetpgrp(self.controlling_terminal, self.childpid)
signal.signal(signal.SIGTTOU, hdlr)
self.is_foreground = True
self.exit_signals = dict((s, signal.signal(s, self._signal_hdlr))
for s in self.SIGNALS)
def stop(self):
try:
for s in self.SIGNALS:
#don't get interrupted while cleaning everything up
signal.signal(s, signal.SIG_IGN)
self.is_stopped = True
if self.is_foreground:
os.tcsetpgrp(self.controlling_terminal, self.orig_fore_pg)
os.close(self.controlling_terminal)
self.is_foreground = False
try:
os.kill(self.childpid, signal.SIGCONT)
except OSError:
"""
can occur if process finished and one of:
- was reaped by another process
- if parent explicitly ignored SIGCHLD
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
- parent has the SA_NOCLDWAIT flag set
"""
pass
os.setpgrp() # leave the child's process group so I won't get signals
try:
os.killpg(self.childpid, signal.SIGINT)
time.sleep(self.time_to_die) # let processes end gracefully
os.killpg(self.childpid, signal.SIGKILL) # In case process gets stuck while dying
os.waitpid(self.childpid, 0) # reap Zombie child process
except OSError as e:
pass
finally:
for s, hdlr in self.exit_signals.iteritems():
signal.signal(s, hdlr) # reset default handlers
def __enter__(self):
if self.is_stopped:
self.start()
def __exit__(self, exit_type, value, traceback):
if not self.is_stopped:
self.stop()
感谢 Malcolm Handley 的初始设计。在linux上用python2.7完成。
【讨论】:
【参考方案12】:你可以试试subalive
,这是我为类似问题写的一个包。它通过RPC使用周期性的alive ping,当master由于某种原因停止alive ping时,slave进程会自动终止。
https://github.com/waszil/subalive
主实例:
from subalive import SubAliveMaster
# start subprocess with alive keeping
SubAliveMaster(<path to your slave script>)
# do your stuff
# ...
从属子进程示例:
from subalive import SubAliveSlave
# start alive checking
SubAliveSlave()
# do your stuff
# ...
【讨论】:
【参考方案13】:这就是我为我的 posix 应用所做的:
当你的应用存在时,调用这个类的 kill() 方法: http://www.pixelbeat.org/libs/subProcess.py
此处使用示例: http://code.google.com/p/fslint/source/browse/trunk/fslint-gui#608
【讨论】:
【参考方案14】:python 代码帮助: http://docs.python.org/dev/library/subprocess.html#subprocess.Popen.wait
【讨论】:
虽然理论上这可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。以上是关于确保子进程在退出 Python 程序时死亡的主要内容,如果未能解决你的问题,请参考以下文章
当父进程死亡时,如何杀死使用 subprocess.check_output() 创建的 python 子进程?