75

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

如果不是,我应该遍历所有发出的杀戮然后杀死-9吗?有什么更清洁的吗?

4

14 回答 14

57

您可以为此使用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
于 2008-11-26T13:36:39.857 回答
50

在 *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

于 2008-11-26T22:02:49.697 回答
16

只有subprocess.Popen.wait()这样才能保证他们已经死了。事实上,POSIX 操作系统要求您等待您的孩子。许多*nix 将创建一个“僵尸”进程:父母没有等待的死去的孩子。

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

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

如果孩子是一个“永远服务”的循环,并且不是为了终止而设计的,你应该杀死它或者提供一些输入或消息来强制它终止。


编辑。

在标准操作系统中,您拥有os.kill( PID, 9 ). 顺便说一句,杀死 -9 很苛刻。如果您可以使用 SIGABRT (6?) 或 SIGTERM (15) 杀死它们,那就更有礼貌了。

在 Windows 操作系统中,你没有一个os.kill可以工作的。查看此ActiveState Recipe以终止 Windows 中的进程。

我们有作为 WSGI 服务器的子进程。为了终止它们,我们在一个特殊的 URL 上执行 GET;这会导致孩子清理并退出。

于 2008-11-26T10:56:21.287 回答
8

找出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)) 
于 2017-04-01T03:23:26.857 回答
6

警告:仅限 Linux!您可以让您的孩子在父母去世时收到信号。

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

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

这说明了什么:

  • 启动子进程:sleep 100
  • 在分叉和执行子进程之前,子进程注册“当我的父母终止时向我发送 SIGKILL”。
于 2014-12-04T13:59:25.873 回答
4

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

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

setsid将在新会话中运行程序,从而为它及其子进程分配一个新进程组。因此调用os.killpg它也不会降低你自己的 python 进程。

于 2014-03-22T19:46:18.043 回答
4

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 来执行此操作,但您必须分别处理异常和非异常情况。

于 2015-01-08T02:17:07.820 回答
3

民意调查( )

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

于 2008-11-26T10:32:24.970 回答
3

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

这是一个现有的python实现

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

于 2016-02-22T22:17:02.460 回答
2

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

您可能会违反封装并测试所有 Popen 进程是否已通过执行终止

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

如果不是,我应该遍历所有发出的杀戮然后杀死-9吗?有什么更清洁的吗?

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

于 2008-11-26T10:54:03.553 回答
2

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

于 2016-12-29T14:39:15.580 回答
0

你可以试试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
# ...
于 2018-10-12T11:33:43.833 回答
-1

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

当您的应用程序存在时,调用此类的 kill() 方法: http ://www.pixelbeat.org/libs/subProcess.py

此处使用示例: http ://code.google.com/p/fslint/source/browse/trunk/fslint-gui#608

于 2008-11-26T11:39:11.123 回答
-1

python代码帮助: http: //docs.python.org/dev/library/subprocess.html#subprocess.Popen.wait

于 2012-03-06T05:48:02.733 回答