相关文章推荐
可爱的可乐  ·  WPF ...·  6 月前    · 
听话的板栗  ·  java.lang.UnsatisfiedL ...·  1 年前    · 
subprocess.Popen(arg)

有什么方法可以确保在父类异常终止时将其杀死?我需要它在Windows和Linux上都能工作。我知道这个solution for Linux.

Edit:

如果存在使用不同方法启动进程的解决方案,那么用subprocess.Popen(arg)启动子进程的要求就可以放宽。

5 个评论
这很模糊,你能给出一些更多的细节吗?也许描述一下父进程和子进程是什么?
jfs
the first solution from the link you provided works on Windows too.
@J.F.Sebastian:当然,但如果进程被sigkill终止的话,第二种方法也是可行的。
jfs
there is no sigkill on Windows
@J.F.Sebastian:让我重新表述一下。如果父进程因任何原因终止,子进程必须退出。第一个解决方案并不能保证这一点。
python
linux
windows
subprocess
kill
user443854
user443854
发布于 2014-05-03
3 个回答
Nathaniel J. Smith
Nathaniel J. Smith
发布于 2022-09-09
已采纳
0 人赞同

呵,我昨天自己还在研究这个问题呢!假设你不能改变孩子的程序。

在Linux上,prctl(PR_SET_PDEATHSIG, ...)可能是唯一可靠的选择。(如果绝对需要杀死子进程,那么你可能想把死亡信号设置为SIGKILL而不是SIGTERM;你链接的代码使用SIGTERM,但如果子进程愿意,它可以选择忽略SIGTERM)。

在Windows上,最可靠的选择是使用一个Job object.这个想法是,你创建一个 "作业"(一种进程的容器),然后你把子进程放入作业中,并设置一个神奇的选项,说 "当没有人持有这个作业的'句柄'时,就杀死其中的进程"。默认情况下,作业的唯一 "句柄 "是你的父进程所持有的句柄,当父进程死亡时,操作系统将浏览并关闭其所有句柄,然后注意到这意味着该作业没有开放的句柄了。所以它就会按照要求杀死这个子进程。(如果你有多个子进程,你可以把它们都分配给同一个作业。)这个答案有示例代码来做这个,使用win32api模块。该代码使用CreateProcess来启动子程序,而不是subprocess.Popen。原因是,他们需要为生成的子程序获得一个 "进程句柄",而CreateProcess默认返回这个句柄。如果你想使用subprocess.Popen,那么这里有一份(未经测试的)该答案中的代码副本,它使用subprocess.PopenOpenProcess而不是CreateProcess

import subprocess
import win32api
import win32con
import win32job
hJob = win32job.CreateJobObject(None, "")
extended_info = win32job.QueryInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation)
extended_info['BasicLimitInformation']['LimitFlags'] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
win32job.SetInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation, extended_info)
child = subprocess.Popen(...)
# Convert process id to process handle:
perms = win32con.PROCESS_TERMINATE | win32con.PROCESS_SET_QUOTA
hProcess = win32api.OpenProcess(perms, False, child.pid)
win32job.AssignProcessToJobObject(hJob, hProcess)

从技术上讲,这里有一个小小的竞赛条件,万一孩子在PopenOpenProcess的调用之间死亡,你可以决定是否要担心这个问题。

使用作业对象的一个缺点是,当在Vista或Win7上运行时,如果你的程序是从Windows外壳启动的(即通过点击一个图标),那么可能会出现已经是一个分配的工作对象试图创建一个新的工作对象将会失败。Win8解决了这个问题(通过允许作业对象嵌套),或者如果你的程序是从命令行运行,那么它应该是好的。

If you 可以修改子代(例如,像使用multiprocessing时),那么最好的选择可能是以某种方式将父代的PID传递给子代(例如,作为一个命令行参数,或在args=的参数中传递给multiprocessing.Process),然后。

在POSIX上:在子程序中产生一个线程,它只是偶尔调用os.getppid(),如果返回值不再匹配从父程序传入的pid,那么就调用os._exit()。(这种方法可以移植到所有的Unix系统,包括OS X,而prctl的技巧是Linux特有的)。

在Windows上。在使用OpenProcessos.waitpid的子程序中产生一个线程。使用ctypes的例子。

from ctypes import WinDLL, WinError
from ctypes.wintypes import DWORD, BOOL, HANDLE
# Magic value from http://msdn.microsoft.com/en-us/library/ms684880.aspx
SYNCHRONIZE = 0x00100000
kernel32 = WinDLL("kernel32.dll")
kernel32.OpenProcess.argtypes = (DWORD, BOOL, DWORD)
kernel32.OpenProcess.restype = HANDLE
parent_handle = kernel32.OpenProcess(SYNCHRONIZE, False, parent_pid)
# Block until parent exits
os.waitpid(parent_handle, 0)
os._exit(0)

这就避免了我提到的工作对象的任何可能的问题。

If you want to be really, really sure, then you 可以 combine all these solutions.

希望这对您有所帮助!

避免你提到的竞赛条件的方法之一是在启动子进程之前将自己加入作业对象;子进程将继承成员资格。 另一种方法是暂停启动子进程,只有在将其添加到作业中后才恢复。
对于Windows 7,shell的作业对象允许脱离,所以你可以使用创建标志CREATE_BREAKAWAY_FROM_JOB来允许将进程添加到一个新的作业中。
我不明白,为什么谋杀孩子的过程会如此复杂?
@CharlieParker 这个问题是关于如何处理父代非正常终止的情况。如果父代出现故障或被强行终止(例如kill -9),那么它就没有机会杀害子代。
这对很多人来说可能是显而易见的,但要真正终止,需要在进程不再需要后加上win32job.TerminateJobObject(hJob, hProcess)
Nick
Nick
发布于 2022-09-09
0 人赞同

Popen对象提供终止和杀死方法。

https://docs.python.org/2/library/subprocess.html#subprocess.Popen.terminate

这些信号为你发送SIGTERM和SIGKILL信号。 你可以做类似于下面的事情。

from subprocess import Popen
p = None
    p = Popen(arg)
    # some code here
except Exception as ex:
    print 'Parent program has exited with the below error:\n{0}'.format(ex)
    if p:
        p.terminate()

你是对的--上述代码不能防止硬崩溃或有人杀死你的进程。在这种情况下,你可以尝试将子进程包装在一个类中,并采用轮询模型来监视父进程。 请注意psutil是非标准的。

import os
import psutil
from multiprocessing import Process
from time import sleep
class MyProcessAbstraction(object):
    def __init__(self, parent_pid, command):
        @type parent_pid: int
        @type command: str
        self._child = None
        self._cmd = command
        self._parent = psutil.Process(pid=parent_pid)
    def run_child(self):
        Start a child process by running self._cmd. 
        Wait until the parent process (self._parent) has died, then kill the 
        child.
        print '---- Running command: "%s" ----' % self._cmd
        self._child = psutil.Popen(self._cmd)
            while self._parent.status == psutil.STATUS_RUNNING:
                sleep(1)
        except psutil.NoSuchProcess:
        finally:
            print '---- Terminating child PID %s ----' % self._child.pid
            self._child.terminate()
if __name__ == "__main__":
    parent = os.getpid()
    child = MyProcessAbstraction(parent, 'ping -t localhost')
    child_proc = Process(target=child.run_child)
    child_proc.daemon = True
    child_proc.start()
    print '---- Try killing PID: %s ----' % parent
    while True:
        sleep(1)

在这个例子中,我运行 "ping -t localhost",因为它将永远运行。如果你杀死父进程,子进程(ping命令)也会被杀死。

这并没有回答这个问题。如果一个父进程崩溃了,谁会去调用p.terminate()?我正在寻找一种方法,在Windows上,启动一个进程,当它的父进程因任何原因终止时,它就退出。这在Linux上是可以做到的。
Nick
你是对的,它没有解决你的问题。希望上面的编辑能做到这一点。
整洁。而且看起来便于携带。我喜欢它。
这......根本不起作用,据我所知,即使是在编辑之后。看起来你是在建议父代应该观察自己是否已经死亡,如果是的话,就杀死孩子。很明显,这没有意义。也许你的意思是,这个例子的代码应该被分割成一个新的程序,所以父程序产生了两个进程:子程序,以及一个监视父程序和子程序的看门狗。但这只是重现了原来的问题--如果看门狗异常终止会怎样?谁来监视看门狗?
啊,我明白了,我错过了例子中的滚动条,它整齐地剪掉了if __name__ == "__main__":块。有了这个,就更有意义了!尽管如此,与使用操作系统级工具(Linux和Windows都有)的更多解决方案相比,这种方法似乎不必要地复杂和不可靠,并且创造了新的问题机会--例如,目前编写的代码使父代无法监控子代的生活或获得退出代码,并且如果运行多个子代,会泄露看门狗进程。
Steve Jorgensen
Steve Jorgensen
发布于 2022-09-09
0 人赞同

因为据我所知,当父进程中有任何线程在运行时,PR_SET_PDEATHSIG的解决方案可能会导致死锁,所以我不想使用这个方法,而是想出了另一个方法。我创建了一个单独的自动终止进程,它可以检测其父进程何时完成,并杀死作为其目标的其他子进程。

为了实现这一目标,你需要pip install psutil,然后编写类似于以下的代码。

def start_auto_cleanup_subprocess(target_pid):
    cleanup_script = f"""
import os
import psutil
import signal
from time import sleep
    # Block until stdin is closed which means the parent process
    # has terminated.                                           
    input()                                                     
except Exception:                                               
    # Should be an EOFError, but if any other exception happens,
    # assume we should respond in the same way.                 
if not psutil.pid_exists({target_pid}):              
    # Target process has already exited, so nothing to do.      
    exit()                                                      
os.kill({target_pid}, signal.SIGTERM)                           
for count in range(10):                                         
    if not psutil.pid_exists({target_pid}):  
        # Target process no longer running.        
        exit()
    sleep(1)
os.kill({target_pid}, signal.SIGKILL)                           
# Don't bother waiting to see if this works since if it doesn't,
# there is nothing else we can do.                              
    return Popen(
            sys.executable,  # Python executable
            '-c', cleanup_script