簡體   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