Python:当父进程死亡时如何杀死子进程? [英] Python: how to kill child process(es) when parent dies?

查看:45
本文介绍了Python:当父进程死亡时如何杀死子进程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

子进程开始于

subprocess.Popen(arg)

当父进程异常终止时,有没有办法确保它被杀死?我需要它在 Windows 和 Linux 上都能工作.我知道这个 Linux 解决方案.

Is there a way to ensure it is killed when parent terminates abnormally? I need this to work both on Windows and Linux. I am aware of this solution for Linux.

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

the requirement of starting a child process with subprocess.Popen(arg) can be relaxed, if a solution exists using a different method of starting a process.

推荐答案

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

Heh, I was just researching this myself yesterday! Assuming you can't alter the child program:

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

On Linux, prctl(PR_SET_PDEATHSIG, ...) is probably the only reliable choice. (If it's absolutely necessary that the child process be killed, then you might want to set the death signal to SIGKILL instead of SIGTERM; the code you linked to uses SIGTERM, but the child does have the option of ignoring SIGTERM if it wants to.)

在 Windows 上,最可靠的选项是使用 工作对象.这个想法是您创建一个作业"(一种进程的容器),然后将子进程放入作业中,并设置魔术选项,表示当没有人持有此作业的'句柄'时,然后杀死其中的进程".默认情况下,作业的唯一句柄"是您的父进程持有的,当父进程死亡时,操作系统将检查并关闭其所有句柄,然后注意这意味着没有打开的句柄工作.因此,它会根据要求杀死孩子.(如果您有多个子进程,则可以将它们全部分配给同一个作业.)此答案 提供了用于执行此操作的示例代码这个,使用 win32api 模块.该代码使用 CreateProcess 来启动子进程,而不是 subprocess.Popen.原因是他们需要为生成的孩子获得一个进程句柄",并且 CreateProcess 默认返回这个.如果您更愿意使用 subprocess.Popen,那么这里是该答案中代码的(未经测试的)副本,它使用 subprocess.PopenOpenProcess 而不是 CreateProcess:

On Windows, the most reliable options is to use a Job object. The idea is that you create a "Job" (a kind of container for processes), then you place the child process into the Job, and you set the magic option that says "when no-one holds a 'handle' for this Job, then kill the processes that are in it". By default, the only 'handle' to the job is the one that your parent process holds, and when the parent process dies, the OS will go through and close all its handles, and then notice that this means there are no open handles for the Job. So then it kills the child, as requested. (If you have multiple child processes, you can assign them all to the same job.) This answer has sample code for doing this, using the win32api module. That code uses CreateProcess to launch the child, instead of subprocess.Popen. The reason is that they need to get a "process handle" for the spawned child, and CreateProcess returns this by default. If you'd rather use subprocess.Popen, then here's an (untested) copy of the code from that answer, that uses subprocess.Popen and OpenProcess instead of 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 调用之间死亡,您可以决定是否要担心这一点.

Technically, there's a tiny race condition here in case the child dies in between the Popen and OpenProcess calls, you can decide whether you want to worry about that.

使用作业对象的一个​​缺点是,在 Vista 或 Win7 上运行时,如果您的程序是从 Windows shell 启动的(即通过单击图标),则可能会出现 已经是分配的作业对象 并且尝试创建新的作业对象将失败.Win8 修复了这个问题(通过允许嵌套作业对象),或者如果您的程序是从命令行运行的,那么应该没问题.

One downside to using a job object is that when running on Vista or Win7, if your program is launched from the Windows shell (i.e., by clicking on an icon), then there will probably already be a job object assigned and trying to create a new job object will fail. Win8 fixes this (by allowing job objects to be nested), or if your program is run from the command line then it should be fine.

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

If you can modify the child (e.g., like when using multiprocessing), then probably the best option is to somehow pass the parent's PID to the child (e.g. as a command line argument, or in the args= argument to multiprocessing.Process), and then:

在 POSIX:在子进程中产生一个线程,它偶尔会调用 os.getppid(),如果返回值停止匹配从父进程传入的 pid,则调用 os._exit().(这种方法适用于所有 Unix,包括 OS X,而 prctl 技巧是 Linux 特定的.)

On POSIX: Spawn a thread in the child that just calls os.getppid() occasionally, and if the return value ever stops matching the pid passed in from the parent, then call os._exit(). (This approach is portable to all Unixes, including OS X, while the prctl trick is Linux-specific.)

在 Windows 上:在使用 OpenProcessos.waitpid 的子进程中生成一个线程.使用 ctypes 的示例:

On Windows: Spawn a thread in the child that uses OpenProcess and os.waitpid. Example using 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)

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

This avoids any of the possible issues with job objects that I mentioned.

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

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

希望有帮助!

这篇关于Python:当父进程死亡时如何杀死子进程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆