Python:当父进程死亡时如何杀死子进程?

2024-10-11 08:36:00
admin
原创
74
摘要:问题描述:子进程启动于subprocess.Popen(arg) 有没有办法确保当父进程异常终止时将其终止?我需要它在 Windows 和 Linux 上都能工作。我知道Linux 有这个解决方案。编辑:subprocess.Popen(arg)如果存在使用不同方法启动进程的解决方案,则可以放宽启动子进程的要...

问题描述:

子进程启动于

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.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()。 (此方法可移植到所有 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()))

比设置特定于操作系统的父子绑定简单得多,并且我认为在大多数情况下应该足够好。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用