繁体   English   中英

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

[英]Python: how to kill child process(es) when parent dies?

子进程开始于

subprocess.Popen(arg)

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

编辑:

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

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

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

在Windows上,最可靠的选择是使用Job对象 想法是创建一个“作业”(一种用于流程的容器),然后将子流程放入作业中,并设置魔术选项,指出“当没有人握住该作业的“手柄”时,然后杀死其中的进程”。 默认情况下,作业的唯一“句柄”是父进程持有的句柄,并且当父进程死掉时,操作系统将遍历并关闭其所有句柄,然后注意这意味着没有用于工作。 因此,它会按要求杀死孩子。 (如果您有多个子进程,则可以将它们全部分配给同一作业。) 此答案包含使用win32api模块执行此操作的示例代码。 该代码使用CreateProcess而不是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传递给子级(例如,作为命令行参数,或者在multiprocessing.Processargs=参数中),接着:

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

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

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

希望有帮助!

Popen对象提供了terminate和kill方法。

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()

更新:

您是正确的-上面的代码无法防止严重崩溃或杀死您的进程。 在这种情况下,您可以尝试将子进程包装在类中,并采用轮询模型来监视父进程。 请注意,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命令)也将被杀死。

因为,据我所知,当任何线程在父进程中运行时, 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并在终止序列期间轮询目标子进程的状态,监控,然后如果终止没有迅速工作就杀死。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM