Python:当父母去世时如何杀死子进程?

use*_*854 23 python linux windows subprocess kill

子进程以#开头

subprocess.Popen(arg)
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

Nat*_*ith 26

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

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

在Windows上,最可靠的选项是使用Job对象.这个想法是你创建一个"Job"(一种进程的容器),然后你将子进程放入Job中,然后你设置一个魔术选项,说"当没有人拥有这个Job的'handle'时,然后杀死其中的进程".默认情况下,作业的唯一"句柄"是父进程占用的句柄,当父进程终止时,操作系统将通过并关闭其所有句柄,然后注意这意味着没有打开的句柄工作.所以它按照要求杀死了孩子.(如果您有多个子进程,则可以将它们全部分配给同一作业.)此答案包含使用该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)
Run Code Online (Sandbox Code Playgroud)

从技术上讲,这里有一个很小的竞争条件,以防孩子在电话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)
Run Code Online (Sandbox Code Playgroud)

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

如果你想真正,非常肯定,那么你可以结合所有这些解决方案.

希望有所帮助!

  • 避免竞争条件的一种方法是在启动子进程之前将自己添加到作业对象中; 孩子将继承会员资格.另一种方法是启动子进程暂停,只有在将其添加到作业后才恢复它. (3认同)

Nic*_*ick 8

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()
Run Code Online (Sandbox Code Playgroud)

更新:

你是对的 - 上面的代码不能防止硬崩溃或有人杀死你的进程.在这种情况下,您可以尝试将子进程包装在类中,并使用轮询模型来监视父进程. 请注意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)
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我运行'ping -t localhost'b/c,它将永远运行.如果您终止父进程,子进程(ping命令)也将被终止.

  • 这并没有回答这个问题。如果父进程崩溃,谁会调用 `p.terminate()`?我正在寻找一种在 Windows 上启动进程的方法,以便在其父进程因任何原因终止时退出。这在 Linux 上是可以做到的。 (3认同)
  • 啊,我看到我错过了示例中的滚动条,它巧妙地剪掉了 `if __name__ == "__main__":` 块。更有意义!尽管如此,与更多使用操作系统级工具(Linux 和 Windows 都可用)的解决方案相比,这种方法似乎不必要地复杂和不可靠,并且为问题创造了新的机会——例如,当前编写的代码使得parent 监视孩子的生活或获取退出代码,如果运行多个孩子,则会泄漏看门狗进程。 (2认同)