确保子进程在退出 Python 程序时死亡

有没有办法确保所有创建的子进程在 Python 程序退出时都已死亡?我所说的子流程是指那些用子流程创建的。Popen ().

如果没有,我是否应该遍历所有发出的 kill,然后杀死 -9? 有什么更干净的吗?

82545 次浏览

民意调查()

检查子进程是否已终止。 返回返回代码属性。

有没有办法确保所有创建的子进程在 Python 程序退出时都已死亡?我所说的子流程是指那些用子流程创建的。Popen ().

您可能会违反所有 Popen 进程已经终止的封装和 测试

subprocess._cleanup()
print subprocess._active == []

如果没有,我是否应该遍历所有发出的 kill,然后杀死 -9? 有什么更干净的吗?

如果不出去杀死所有幸存者,就无法确保所有子进程都已死亡。但是如果您有这个问题,那可能是因为您有一个更深层次的设计问题。

subprocess.Popen.wait()是确保他们死亡的唯一方法。事实上,POSIX 操作系统要求您等待您的孩子。许多 * nix 将创建一个“僵尸”进程: 一个父母没有等待的死去的孩子。

如果子元素写得相当好,它就会终止。孩子们经常从 PIPE 读书。关闭输入对于孩子来说是一个很大的提示,即它应该关闭商店并退出。

如果这个孩子有 bug 而且没有终止,你可能必须杀死它。你应该修复这个 bug。

如果子循环是一个“ server-forever”循环,并且不设计为终止,那么您应该杀死它,或者提供一些输入或消息来强制它终止。


剪辑。

在标准操作系统中,有 os.kill( PID, 9 )。顺便说一句,杀死 -9很残酷。如果你能用 SIGABRT (6?)杀死他们或 SIGTERM (15)这样更礼貌。

在 Windows 操作系统中,没有可以工作的 os.kill。请查看此 ActiveState 食谱以终止 Windows 中的进程。

我们有 WSGI 服务器的子进程。为了终止它们,我们对一个特殊的 URL 执行一个 GET 操作; 这会导致子元素被清除并退出。

这就是我为我的 posx 应用程序所做的:

当应用程序存在时,调用这个类的 kill ()方法: Http://www.pixelbeat.org/libs/subprocess.py

这里的示例使用: Http://code.google.com/p/fslint/source/browse/trunk/fslint-gui#608

您可以为此使用 < strong > 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 方法

这里有一种在窗口中终止进程的方法。您的 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

在 * 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 (a.k.a kill -9)。在两个信号之间稍等一会儿,让进程有机会在 kill -9之前干净地退出。

我需要这个问题的一个小变种(清理子进程,但不退出 Python 程序本身) ,因为这里没有在其他答案中提到它:

p=subprocess.Popen(your_command, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), 15)

setsid将在一个新的会话中运行该程序,从而为其及其子进程分配一个新的进程组。因此,在它上面调用 os.killpg也不会导致您自己的 python 进程崩溃。

警告: 仅适用于 Linux! 您可以让您的孩子在其父母死亡时接收到一个信号。

首先安装 python-prctl = = 1.5.0,然后修改父代码以启动子进程,如下所示

subprocess.Popen(["sleep", "100"], preexec_fn=lambda: prctl.set_pdeathsig(signal.SIGKILL))

上面写的是:

  • 启动子进程: sleep 100
  • 在子进程的 fork 和 exec 之后,子进程注册为“ send me a SIGKILL 当我的父母终止”。

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/竹篮打水一场空,但是您必须分别处理异常情况和非异常情况。

Windows 的解决方案可能是使用 win32作业 API,例如 如何自动销毁 Windows 中的子进程?

下面是一个现有的 python 实现

Https://gist.github.com/ubershmekel/119697afba2eaecc6330

我实际上需要这样做,但它涉及运行远程命令。我们希望能够通过关闭到服务器的连接来停止这些进程。另外,例如,如果您正在运行 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。

找到 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))

你可以试试 subalive,一个我为类似问题编写的软件包。它通过 RPC 使用周期性的活动 ping,当主控由于某种原因停止活动 ping 时,从进程自动终止。

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
# ...

有可能通过产生一个单独的过程来监督破坏,从而对窗户获得更多的保证。

import subprocess
import sys
import os


def terminate_process_on_exit(process):
if sys.platform == "win32":
try:
# Or provide this script normally.
# Here just to make it somewhat self-contained.
# see https://stackoverflow.com/a/22559493/3763139
# see https://superuser.com/a/1299350/388191
with open('.process_watchdog_helper.bat', 'x') as file:
file.write(""":waitforpid
tasklist /nh /fi "pid eq %1" 2>nul | find "%1" >nul
if %ERRORLEVEL%==0 (
timeout /t 5 /nobreak >nul
goto :waitforpid
) else (
wmic process where processid="%2" call terminate >nul
)""")
except:
pass
        

# After this spawns we're pretty safe. There is a race, but we do what we can.
subprocess.Popen(
['.process_watchdog_helper.bat', str(os.getpid()), str(process.pid)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)


# example
class DummyProcess:
def __init__(self, pid):
self.pid = pid
set_terminate_when_this_process_dies(DummyProcess(7516))