在Windows上,
subprocess.Popen.terminate
调用win32的TerminalProcess
。 但是,我看到的行为是我尝试终止的进程的子进程仍在运行。为什么会这样?如何确保杀死进程启动的所有子进程?subprocess.Popen.terminate
调用win32的TerminalProcess
。 但是,我看到的行为是我尝试终止的进程的子进程仍在运行。为什么会这样?如何确保杀死进程启动的所有子进程?通过使用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)
parent.children(recursive=True)
通过将父进程链接到子进程来动态构建树形结构,因此它无法找到孤立的进程(即如果父进程已死亡)。 - Eryk Sun使用带有 /T
标志的 taskkill
命令
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)
这是一个Job对象方法的示例代码,但是它使用win32api.CreateProcess
而不是subprocess
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)
我曾经遇到过同样的问题,解决方法是通过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构建进程。在Windows 7上,简单的process.terminate()
或process.kill()
都不起作用,上面提到的psutils
选项也不行,但是这个方法可以。 - Ken Pronovici# 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)
如果像我一样,你正在使用类似以下逻辑从vbs脚本中创建对象,则上述任何答案都不起作用:
Set oExcel = CreateObject("Excel.Application")
这是因为在Windows中,这会将Excel作为服务生成,并且它的所有者是svchost.exe而不是您的VBS脚本。感谢spokes提供的答案,在诊断此问题方面非常有帮助。
我相当粗略地处理了这个问题,基本上创建了一个Excel进程列表,然后启动它,在之后获取另一个Excel进程列表并进行比较,新PID成为我的新Excel脚本。然后,如果需要终止它,我通过其PID标识它并将其终止。
this_script_pid = 0
try:
running_excel_pids = [pid for pid in psutil.pids() \
if psutil.Process(pid).name() == "EXCEL.EXE"] # record all instances of excel before this starts
p = subprocess.Popen(<starts vbs script that starts excel>)
time.sleep(0.05) # give the script time to execute
for pid in [p for p in psutil.pids() if psutil.Process(p).name() == "EXCEL.EXE"]:
if pid not in running_excel_pids:
this_script_pid = pid
break
p.communicate() # required to make our python program wait for the process to end
except:
p.terminate() # kill the parent script
if this_script_pid != 0:
print("killing individual script")
psutil.Process(this_script_pid).kill()
else:
for pid in [p for p in psutil.pids() if psutil.Process(p).name() == "EXCEL.EXE"]:
if (pid not in running_excel_pids) and (psutil.Process(pid).parent().name()=="svchost.exe"):
proc = psutil.Process(pid)
proc.kill()
exit() # gracefully quit
请注意,上述仅适用于我的特定情况,即使我已经尝试尽可能地针对性强,它几乎肯定不应在多线程环境中使用。
0.05秒的等待是根据经验发现的。 0.01秒太短了,0.03秒可以工作,因此0.05秒看起来很安全。
除块中的else只是一个捕获所有异常的语句,以防它无法记录创建的PID,它将杀死自脚本开始以来启动为服务的所有Excel进程。
更简洁明了的答案可能是扩展Spokes链接的答案并从shell中运行excel,但我没有时间去解决这个问题。