2008-11-26 120 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))