确保subprocess在退出Python程序时死亡

有没有办法确保在Python程序退出时所有创build的subprocess都已经死了? subprocess我的意思是用subprocess.Popen()创build的。

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

你可以使用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中杀死进程的方法。 你的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的,也许使用进程组可以帮助你 – 你也可以捕捉subprocess产生的subprocess。

 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它之前干净地退出。

subprocess.Popen.wait()是确保它们已经死亡的唯一方法。 事实上,POSIX OS要求你等待你的孩子。 许多* nix将创build一个“僵尸”的过程:父母没有等待的死亡孩子。

如果孩子写得相当好,就会终止。 孩子经常从PIPE读书。 closuresinput对孩子来说是一个很大的暗示,它应该closures商店并退出。

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

如果孩子是一个“永远服务”的循环,而不是devise终止,你应该杀死它或提供一些input或消息,这将迫使终止。


编辑。

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

在Windows操作系统中,您没有os.kill 。 看看这个ActiveState配方在Windows中终止进程。

我们有WSGI服务器的subprocess。 要终止他们,我们做一个特殊的URL的GET; 这会导致孩子清理并退出。

poll()

检查subprocess是否已经终止。 返回returncode属性。

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

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

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

这说的是:

  • 启动subprocess:睡眠100
  • 在子程序执行之前分叉之后,孩子注册为“当我父母终止时发送一个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 / except / finally来做到这一点,但是你必须分别处理例外情况和非例外情况。

有没有办法确保在Python程序退出时所有创build的subprocess都已经死了? subprocess我的意思是用subprocess.Popen()创build的。

你可以违反封装,并testing所有的Popen进程已经终止

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

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

你不能确保所有的subprocess都没有出去杀死每一个幸存者。 但是,如果你有这个问题,可能是因为你有更深的devise问题。

我其实需要这样做,但它涉及到运行远程命令。 我们希望能够通过closures与服务器的连接来停止进程。 另外,例如,如果你正在python repl中运行,你可以select以前台运行,如果你想能够使用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的最初devise。 在linux上完成python2.7。

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

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

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

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

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

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

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

一个解决scheme的Windows可能是使用win32作业api例如, 如何自动销毁Windows中的subprocess?

这是一个现有的Python实现

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

找出一个Linux的解决scheme(不安装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))