一尘不染

Python:当父异常终止时,如何杀死子进程?

linux

子进程开始于

subprocess.Popen(arg)

有没有办法确保在父异常终止时将其杀死?我需要在Windows和Linux上都能使用。

编辑:

subprocess.Popen(arg)如果存在使用其他启动进程的方法的解决方案,则可以放宽启动子进程的要求。


阅读 869

收藏
2020-06-02

共1个答案

一尘不染

呵呵,我昨天自己在研究这个!假设您无法更改子程序:

在Linux上,prctl(PR_SET_PDEATHSIG,...)可能是唯一可靠的选择。(如果绝对有必要终止子进程,那么您可能希望将终止信号设置为SIGKILL而不是SIGTERM;链接到的代码使用SIGTERM,但是子级确实可以选择忽略SIGTERM。

在Windows上,最可靠的选择是使用Job对象。想法是创建一个“作业”(一种用于流程的容器),然后将子流程放入作业中,并设置魔术选项,指出“当没有人握住该作业的“手柄”时,然后杀死其中的进程”。默认情况下,作业的唯一“句柄”是父进程持有的句柄,并且当父进程死掉时,操作系统将遍历并关闭其所有句柄,然后注意这意味着没有用于工作。因此,它会按要求杀死孩子。包含使用该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
Shell启动程序(即,通过单击图标),则可能已经分配了一个作业对象并尝试创建一个作业对象。新的作业对象将失败。Win8可以解决此问题(通过允许嵌套作业对象),或者如果您的程序是从命令行运行的,那么应该可以。

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

在POSIX上:在子级中生成一个os.getppid()偶尔会调用的线程,如果返回值停止匹配从父级传入的pid,则调用os._exit()。(这种方法可移植到包括OS
X在内的所有Unix上,而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)

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

如果您想真正确定,那么可以组合所有这些解决方案。

希望有帮助!

2020-06-02