subprocess:删除Windows中的子进程

Posted

技术标签:

【中文标题】subprocess:删除Windows中的子进程【英文标题】:subprocess: deleting child processes in Windows 【发布时间】:2010-11-16 20:53:40 【问题描述】:

在 Windows 上,subprocess.Popen.terminate 调用 win32 的 TerminalProcess。但是,我看到的行为是我试图终止的进程的子进程仍在运行。这是为什么?如何确保进程启动的所有子进程都被杀死?

【问题讨论】:

这里有 2 个选项 1. 将此 exe 用作子进程,它会为您杀死进程树:latenighthacking.com/projects/2002/kill 2. 将以下 C 代码转换为带有 ctypes 的 Python:***.com/questions/1173342/… Python 和 Windows 哪个版本? 【参考方案1】:

我使用 kevin-smyth 的回答为 subprocess.Popen 创建了一个 drop-in replacement,将创建的子进程限制在匿名作业对象中,设置为关闭时终止:

# coding: utf-8

from subprocess import Popen
import subprocess
import win32job
import win32process
import win32api


class JobPopen(Popen):
    """Start a process in a new Win32 job object.

    This `subprocess.Popen` subclass takes the same arguments as Popen and
    behaves the same way. In addition to that, created processes will be
    assigned to a new anonymous Win32 job object on startup, which will
    guarantee that the processes will be terminated by the OS as soon as
    either the Popen object, job object handle or parent Python process are
    closed.
    """

    class _winapijobhandler(object):
        """Patches the native CreateProcess function in the subprocess module
        to assign created threads to the given job"""

        def __init__(self, oldapi, job):
            self._oldapi = oldapi
            self._job = job

        def __getattr__(self, key):
            if key != "CreateProcess":
                return getattr(self._oldapi, key)  # Any other function is run as before
            else:
                return self.CreateProcess  # CreateProcess will call the function below

        def CreateProcess(self, *args, **kwargs):
            hp, ht, pid, tid = self._oldapi.CreateProcess(*args, **kwargs)
            win32job.AssignProcessToJobObject(self._job, hp)
            win32process.ResumeThread(ht)
            return hp, ht, pid, tid

    def __init__(self, *args, **kwargs):
        """Start a new process using an anonymous job object. Takes the same arguments as Popen"""

        # Create a new job object
        self._win32_job = self._create_job_object()

        # Temporarily patch the subprocess creation logic to assign created
        # processes to the new job, then resume execution normally.
        CREATE_SUSPENDED = 0x00000004
        kwargs.setdefault("creationflags", 0)
        kwargs["creationflags"] |= CREATE_SUSPENDED
        try:
            _winapi = subprocess._winapi  # Python 3
            _winapi_key = "_winapi"
        except AttributeError:
            _winapi = subprocess._subprocess  # Python 2
            _winapi_key = "_subprocess"
        try:
            setattr(subprocess, _winapi_key, JobPopen._winapijobhandler(_winapi, self._win32_job))
            super(JobPopen, self).__init__(*args, **kwargs)
        finally:
            setattr(subprocess, _winapi_key, _winapi)

    def _create_job_object(self):
        """Create a new anonymous job object"""
        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)
        return hjob

    def _close_job_object(self, hjob):
        """Close the handle to a job object, terminating all processes inside it"""
        if self._win32_job:
            win32api.CloseHandle(self._win32_job)
            self._win32_job = None

    # This ensures that no remaining subprocesses are found when the process
    # exits from a `with JobPopen(...)` block.
    def __exit__(self, exc_type, value, traceback):
        super(JobPopen, self).__exit__(exc_type, value, traceback)
        self._close_job_object(self._win32_job)

    # Python does not keep a reference outside of the parent class when the
    # interpreter exits, which is why we keep it here.
    _Popen = subprocess.Popen  
    def __del__(self):
        self._Popen.__del__(self)
        self._close_job_object(self._win32_job)

【讨论】:

在尝试了大约 15 条建议之后,这是唯一对我有用的解决方案。像这样使用 JobPopen 实例化一个子进程: with open(JobPopen(command, stdout=subprocess.PIPE, shell=True)) as p: # YOUR CODE HERE # 然后,此时脚本退出“with open”块- 它将关闭该进程。【参考方案2】:

我遇到了同样的问题,只是通过 windows 命令杀死进程,并带有杀死孩子“/T”的选项

def kill_command_windows(pid):
    '''Run command via subprocess'''
    dev_null = open(os.devnull, 'w')
    command = ['TASKKILL', '/F', '/T', '/PID', str(pid)]
    proc = subprocess.Popen(command, stdin=dev_null, stdout=sys.stdout, stderr=sys.stderr)

【讨论】:

我试图终止一个以subprocess.Popen() 启动的 Gradle 构建过程。一个简单的 process.terminate()process.kill() 在 WINdows 7 上不起作用,上面的 psutils 选项也不起作用,但确实如此。【参考方案3】:

通过使用psutil:

import psutil, os

def kill_proc_tree(pid, including_parent=True):    
    parent = psutil.Process(pid)
    children = parent.children(recursive=True)
    for child in children:
        child.kill()
    gone, still_alive = psutil.wait_procs(children, timeout=5)
    if including_parent:
        parent.kill()
        parent.wait(5)

me = os.getpid()
kill_proc_tree(me)

【讨论】:

该代码看起来只会杀死一级子级,而不是孙级等。如果您使用 cmd.exe 启动构建工具或 .bat/.cmd 文件,可能会出现问题。除非 get_children() 也意味着孙子? 没错。要包括孙子,您应该在 parent.get_children(recursive=True) 中指定“递归”选项 这是一个旧答案,但这正是我所寻找的——一种杀死所有子进程的跨平台方式。谷歌上出现的所有其他答案都声称无法完成。谢谢你的psutil! 请注意,Windows 不维护进程树。 parent.children(recursive=True) 正在通过将父级链接到子级来动态构建树,因此它不会找到孤立进程(即如果父级死亡)。 非常感谢!这正是我想要的。您是否同意让此答案中的代码在开源许可条款下使用? BSD 或 MIT 将是理想的,因为它们与 Numpy、Pandas、Scipy 等兼容。【参考方案4】:

这里是 Job 对象方法的示例代码,但不是 subprocess 它使用 win32api.CreateProcess

import win32process
import win32job
startup = win32process.STARTUPINFO()
(hProcess, hThread, processId, threadId) = win32process.CreateProcess(None, command, None, None, True, win32process.CREATE_BREAKAWAY_FROM_JOB, None, None, startup)

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)
win32job.AssignProcessToJobObject(hJob, hProcess)

【讨论】:

+1:这似乎是最强大的解决方案,即使父进程崩溃也能正常工作。这是the explanation on how it works【参考方案5】:

taskkill/T 标志一起使用

p = subprocess.Popen(...)
<wait>
subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)])

taskkill 的标志有以下文档:

TASKKILL [/S system [/U username [/P [password]]]]
          [/FI filter] [/PID processid | /IM imagename]  [/T] [/F]

/S    system           Specifies the remote system to connect to.
/U    [domain\]user    Specifies the user context under which the
                       command should execute.
/P    [password]       Specifies the password for the given user
                       context. Prompts for input if omitted.
/FI   filter           Applies a filter to select a set of tasks.
                       Allows "*" to be used. ex. imagename eq acme*
/PID  processid        Specifies the PID of the process to be terminated.
                       Use TaskList to get the PID.
/IM   imagename        Specifies the image name of the process
                       to be terminated. Wildcard '*' can be used
                       to specify all tasks or image names.
/T                     Terminates the specified process and any
                       child processes which were started by it.
/F                     Specifies to forcefully terminate the process(es).
/?                     Displays this help message.

或者使用 comtypes 和 win32api 遍历进程树:

def killsubprocesses(parent_pid):
    '''kill parent and all subprocess using COM/WMI and the win32api'''

    log = logging.getLogger('killprocesses')

    try:
        import comtypes.client
    except ImportError:
        log.debug("comtypes not present, not killing subprocesses")
        return

    logging.getLogger('comtypes').setLevel(logging.INFO)

    log.debug('Querying process tree...')

    # get pid and subprocess pids for all alive processes
    WMI = comtypes.client.CoGetObject('winmgmts:')
    processes = WMI.InstancesOf('Win32_Process')
    subprocess_pids =  # parent pid -> list of child pids

    for process in processes:
        pid = process.Properties_('ProcessID').Value
        parent = process.Properties_('ParentProcessId').Value
        log.trace("process %i's parent is: %s" % (pid, parent))
        subprocess_pids.setdefault(parent, []).append(pid)
        subprocess_pids.setdefault(pid, [])

    # find which we need to kill
    log.debug('Determining subprocesses for pid %i...' % parent_pid)

    processes_to_kill = []
    parent_processes = [parent_pid]
    while parent_processes:
        current_pid = parent_processes.pop()
        subps = subprocess_pids[current_pid]
        log.debug("process %i children are: %s" % (current_pid, subps))
        parent_processes.extend(subps)
        processes_to_kill.extend(subps)

    # kill the subprocess tree
    if processes_to_kill:
        log.info('Process pid %i spawned %i subprocesses, terminating them...' % 
            (parent_pid, len(processes_to_kill)))
    else:
        log.debug('Process pid %i had no subprocesses.' % parent_pid)

    import ctypes
    kernel32 = ctypes.windll.kernel32
    for pid in processes_to_kill:
        hProcess = kernel32.OpenProcess(PROCESS_TERMINATE, FALSE, pid)
        if not hProcess:
            log.warning('Unable to open process pid %i for termination' % pid)
        else:
            log.debug('Terminating pid %i' % pid)                        
            kernel32.TerminateProcess(hProcess, 3)
            kernel32.CloseHandle(hProcess)

【讨论】:

谢谢你。【参考方案6】:

把孩子放在一个NTJob object,然后你就可以杀死所有的孩子

【讨论】:

Job 对象似乎是解决这个问题的正确方法;太糟糕了,这没有与子流程模块集成。【参考方案7】:

这是一件很难的事情。 Windows 实际上并不在进程空间中存储进程树。也不可能终止一个进程并指定它的子进程也应该死亡。

解决这个问题的一种方法是使用 taskkill 并告诉它破坏整个树。

另一种方法(假设您正在生成***进程)是使用一个考虑到这种情况而开发的模块:http://benjamin.smedbergs.us/blog/tag/killableprocess/

为了一般地为自己执行此操作,您必须花一些时间向后构建列表。也就是说,一个进程存储指向其父进程的指针,但父进程似乎不存储有关子进程的信息。

因此,您必须查看系统中的所有进程(这实际上并不难),然后通过查看父进程字段自己手动连接这些点。然后,您选择您感兴趣的树并遍历整个过程,依次逐个杀死每个节点。

请注意,当父级死亡时,Windows 不会更新子级的父级指针,因此您的树中可能存在间隙。我不知道你可以对这些做些什么。

【讨论】:

以上是关于subprocess:删除Windows中的子进程的主要内容,如果未能解决你的问题,请参考以下文章

如何在 python 中杀死使用 subprocess.call 调用的子进程? [复制]

如何从 Python 中的子进程向 SSH 传递命令

使用 PATH 变量中的子进程执行自定义命令

通过不使用通信的子进程捕获输出

Python并行操作 - 父子进程 subprocess库

远程服务器上的子进程