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

2024-05-29 11:05:23 发布

您现在位置:Python中文网/ 问答频道 /正文

子进程以

subprocess.Popen(arg)

有没有办法确保在父进程异常终止时终止?我需要这个在Windows和Linux上都能工作。我知道这个solution for Linux

编辑:

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


Tags: 方法编辑for进程linuxwindowsarg解决方案
2条回答

嘿,昨天我自己也在研究呢!假设您不能更改子程序:

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

在Windows上,最可靠的选项是使用Job object。其思想是创建一个“作业”(一种进程容器),然后将子进程放入该作业中,然后设置一个神奇的选项,即“当没有人持有此作业的“句柄”时,然后杀死其中的进程”。默认情况下,作业的唯一“句柄”是父进程持有的句柄,当父进程死亡时,操作系统将遍历并关闭其所有句柄,然后注意这意味着作业没有打开的句柄。所以它会按要求杀死孩子。(如果有多个子进程,则可以将它们全部分配给同一个作业。)This answer有用于执行此操作的示例代码,使用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启动的(即,通过单击图标),那么可能会有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)

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

如果你真的,真的想确定,那么你可以把所有这些解决方案结合起来。

希望能有帮助!

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

https://docs.python.org/2/library/subprocess.html#subprocess.Popen.terminate

它们为你发出信号。 你可以做以下类似的事情:

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命令)也将被杀死。

相关问题 更多 >

    热门问题