Python:当父进程死亡时如何杀死子进程?
- 2024-10-11 08:36:00
- admin 原创
- 74
问题描述:
子进程启动于
subprocess.Popen(arg)
有没有办法确保当父进程异常终止时将其终止?我需要它在 Windows 和 Linux 上都能工作。我知道Linux 有这个解决方案。
编辑:
subprocess.Popen(arg)
如果存在使用不同方法启动进程的解决方案,则可以放宽启动子进程的要求。
解决方案 1:
嘿,我昨天刚刚自己研究过这个问题!假设你不能改变子程序:
在 Linux 上,prctl(PR_SET_PDEATHSIG, ...)
可能是唯一可靠的选择。(如果绝对有必要终止子进程,那么您可能希望将死亡信号设置为 SIGKILL 而不是 SIGTERM;您链接到的代码使用 SIGTERM,但子进程确实可以选择忽略 SIGTERM(如果它愿意的话)。)
在 Windows 上,最可靠的选择是使用Job 对象。这个想法是,您创建一个“Job”(一种进程容器),然后将子进程放入该 Job 中,并设置一个神奇的选项,即“当没有人持有此 Job 的‘句柄’时,则终止其中的进程”。默认情况下,该 Job 的唯一‘句柄’是您的父进程持有的句柄,当父进程死亡时,操作系统将检查并关闭其所有句柄,然后注意到这意味着 Job 没有打开的句柄。然后它会按照要求杀死孩子。(如果您有多个子进程,您可以将它们全部分配给同一个作业。)此答案有使用模块执行此操作的示例代码win32api
。该代码用于CreateProcess
启动子进程,而不是subprocess.Popen
。原因是他们需要为生成的子进程获取一个“进程句柄”,并CreateProcess
默认返回它。如果您愿意使用subprocess.Popen
,那么这里有一个来自该答案的代码(未经测试的)副本,它使用subprocess.Popen
和OpenProcess
而不是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)
从技术上讲,这里有一个微小的竞争条件,以防孩子在Popen
和OpenProcess
调用之间死亡,你可以决定是否要担心这个问题。
使用作业对象的一个缺点是,在 Vista 或 Win7 上运行时,如果您的程序是从 Windows shell 启动的(即通过单击图标),那么可能已经分配了一个作业对象,尝试创建新的作业对象将会失败。Win8 解决了这个问题(通过允许作业对象嵌套),或者如果您的程序是从命令行运行的,那么应该没问题。
如果您可以修改子进程(例如,像使用 时一样multiprocessing
),那么最好的选择是以某种方式将父进程的 PID 传递给子进程(例如作为命令行参数,或args=
在 的参数中multiprocessing.Process
),然后:
在 POSIX 上:在子进程中生成一个线程,该线程os.getppid()
偶尔调用,如果返回值不再与从父进程传入的 pid 匹配,则调用os._exit()
。 (此方法可移植到所有 Unix,包括 OS X,而此prctl
技巧是 Linux 特有的。)
在 Windows 上:在子进程中生成一个使用OpenProcess
和 的线程os.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)
这避免了我提到的任何与作业对象有关的可能问题。
如果您真的想确定,那么您可以将所有这些解决方案结合起来。
希望有帮助!
解决方案 2:
Popen 对象提供终止和终止方法。
https://docs.python.org/2/library/subprocess.html#subprocess.Popen.terminate
这些会为您发送 SIGTERM 和 SIGKILL 信号。您可以执行类似以下操作:
from subprocess import Popen
p = None
try:
p = Popen(arg)
# some code here
except Exception as ex:
print 'Parent program has exited with the below error:
{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)
try:
while self._parent.status == psutil.STATUS_RUNNING:
sleep(1)
except psutil.NoSuchProcess:
pass
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 命令)也将被终止。
解决方案 3:
据我所知,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
try:
# 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.
pass
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
],
stdin=subprocess.PIPE
)
这与我没有注意到的https://stackoverflow.com/a/23436111/396373类似,但我认为我想到的方法对我来说更容易使用,因为清理目标的进程是由父进程直接创建的。另请注意,没有必要轮询父进程的状态,尽管如果psutil
您想尝试终止、监视,然后在终止无法迅速完成时终止,仍然需要在终止序列中使用和轮询目标子进程的状态,如本例所示。
解决方案 4:
使用 SetConsoleCtrlHandler 挂接进程的退出,并终止子进程。我觉得我有点过度了,但它确实有效 :)
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)
def func(x):
print("killed")
if anotherproc:
kill_proc_tree(anotherproc.pid)
kill_proc_tree(os.getpid())
import win32api,shlex
win32api.SetConsoleCtrlHandler(func, True)
PROCESSTORUN="your process"
anotherproc=None
cmdline=f"/c start /wait "{PROCESSTORUN}" "
anotherproc=subprocess.Popen(executable='C:\Windows\system32\cmd.EXE', args=shlex.split(cmdline,posix="false"))
...
run program
...
从以下位置获取 kill_proc_tree:
子进程:在 Windows 中删除子进程
解决方案 5:
如果子进程是你用 Python 编写的,那么一个简单的方法是定期检查父进程是否已退出:
import os, sys, asyncio, psutil
async def check_orphaned():
parent = psutil.Process(os.getppid())
while True:
if not parent.is_running():
sys.exit()
await asyncio.sleep(2.5)
# check if orphaned in the background
orphan_listener_task = asyncio.create_task(check_orphaned()))
比设置特定于操作系统的父子绑定简单得多,并且我认为在大多数情况下应该足够好。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件