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

bxjv4tth  于 2022-10-30  发布在  Python
关注(0)|答案(4)|浏览(314)

子进程以

subprocess.Popen(arg)

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

编辑:

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

of1yzvn4

of1yzvn41#

嘿,我昨天还在研究呢!假设你不能修改子程序:
在Linux上,prctl(PR_SET_PDEATHSIG, ...)可能是唯一可靠的选择。(如果绝对有必要终止子进程,那么您可能希望将终止信号设置为SIGKILL而不是SIGTERM;您链接到的代码使用SIGTERM,但子程序可以根据需要选择忽略SIGTERM。)
在Windows上,最可靠的选项是使用Job object。(一种进程容器),然后将子进程放入作业中,并设置“当没有人持有此作业的”句柄“时,则终止其中的进程”的神奇选项。默认情况下,这个作业的唯一“句柄”是你的父进程持有的那个句柄,当父进程死亡时,操作系统会检查并关闭它所有的句柄,然后注意到这意味着这个作业没有打开的句柄,于是它就杀死了这个子进程,如所要求的那样。(如果您有多个子进程,可以将它们全部分配给同一个作业。)This answer提供了执行此操作的示例代码,使用win32api模块。该代码使用CreateProcess启动子进程,而不是subprocess.Popen。原因是它们需要为派生的子进程获取一个“进程句柄”,而CreateProcess在默认情况下返回此句柄。(未测试)该答案中的代码副本,使用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启动的(即,通过点击图标),那么可能会出现already be a job object assigned,并且试图创建一个新的作业对象会失败。Win8修复了这个问题(通过允许嵌套作业对象),或者如果你的程序是从命令行运行的,那么它应该是好的。
如果您 * 可以 * 修改子进程(例如,使用multiprocessing时),那么最好的选择可能是以某种方式将父进程的PID传递给子进程(例如,作为命令行参数,或在args=参数中传递给multiprocessing.Process),然后:
在POSIX上:在子线程中生成一个线程,该线程只是偶尔调用os.getppid(),如果返回值不再与从父线程传入的pid匹配,则调用os._exit()。(这种方法可移植到所有的Unix,包括OS X,而prctl技巧是Linux特有的。)
在Windows上:在使用OpenProcessos.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)

这避免了我提到的作业对象可能出现的任何问题。
如果你想非常非常确定,那么你可以把所有这些解决方案结合起来。
希望这对你有帮助!

kmb7vmvb

kmb7vmvb2#

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:\n{0}'.format(ex)
    if p:
        p.terminate()

最新消息:
您是正确的--上面的代码不能防止进程硬崩溃或被人终止。在这种情况下,您可以尝试将子进程 Package 在一个类中,并使用轮询模型来监视父进程。请注意,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' B/c,它将永远运行。如果你杀死父进程,子进程(ping命令)也将被杀死。

tpgth1q7

tpgth1q73#

据我所知,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类似,但我认为我提出的方法更容易使用,因为作为清理目标的进程是由父进程直接创建的。还要注意,没有必要轮询父进程的状态,尽管在终止序列期间,如果您想尝试终止、监视然后如果终止不能迅速起作用则将其杀死。

xpszyzbs

xpszyzbs4#

使用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取自:
subprocess: deleting child processes in Windows

相关问题