2008-11-26 129 views
43

有没有一种方法可以确保在Python程序退出时所有创建的子进程都已死亡?子进程我的意思是用subprocess.Popen()创建的那些子进程。确保子进程在退出Python程序时死亡

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

+0

相关:如何终止蟒蛇子与壳牌推出=真(http://stackoverflow.com/q/4789837/ 4279) – jfs 2014-03-22 19:52:50

+1

相关:[Python:当父母死亡时如何杀死子进程?](http://stackoverflow.com/q/23434842/4279) – jfs 2016-02-24 20:31:53

回答

36

你可以为此,请使用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

这里的一个方式杀死窗口的处理。您POPEN对象有一个pid的属性,所以你可以把它通过成功(安装需要pywin32)= win_kill(p.pid)

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 
+0

你能解释一下你在Windows代码中做了些什么吗? – 2014-05-28 07:25:50

+0

为什么'win_kill'需要,因为p.kill()存在?它是否为2.6以前的python用户? – 2016-03-09 21:45:27

+0

是的,我当时相信,2.5仍然被广泛使用,并且p.kill()在windows中不可用。 – monkut 2016-03-10 01:17:51

4

民意调查()

检查子进程已经终止。 返回returncode属性。

2

有没有一种方法,以确保所有已创建子进程正处在一个Python程序的退出时间死了吗?子进程我的意思是用subprocess.Popen()创建的那些子进程。

你可以破坏封装和测试所有POPEN进程已经做

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

终止如果不是这样,我应该遍历所有发行杀死,然后杀死-9?什么更干净?

你不能确保所有的子过程没有出去和杀死每个幸存者都死了。但是如果你有这个问题,那可能是因为你有更深的设计问题。

14

subprocess.Popen.wait()是确保他们死亡的唯一方法。事实上,POSIX OS需要你等待你的孩子。许多* nix会创建一个“僵尸”进程:父母没有等待的死亡孩子。

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

如果孩子有缺陷并且没有终止,你可能不得不杀死它。你应该修复这个错误。

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


编辑。

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

在Windows操作系统中,您没有可用的os.kill。查看ActiveState Recipe终止Windows中的进程。

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

28

在* 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)。在信号之间等待一段时间,让该过程有机会在你清除之前退出。

2

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

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

setsid将在新会话中运行该程序,从而为其及其子节点分配新的进程组。调用os.killpg就不会造成你自己的python进程。

4

警告:仅限Linux!当其父母死亡时,您可以让您的孩子接收到一个信号。

首先安装python-prctl == 1.5。0,那么改变你的父代码来启动你的孩子的过程如下

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

它说的是:

  • 启动子:分叉后和子,孩子的Exec之前入睡100
  • 注册为“当我的父母终止时向我发送SIGKILL ”。
3

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 /做到这一点,除了/最后,但你必须单独处理的特殊和非特殊情况下。

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。

1

找出一个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))