簡體   English   中英

如何處理子進程異常終止?

[英]How to handle abnormal child process termination?

我正在使用 python 3.7 並遵循此文檔 我想要一個進程,它應該產生一個子進程,等待它完成任務,並獲取一些信息。 我使用以下代碼:

if __name__ == '__main__':
    q = Queue()
    p = Process(target=some_func, args=(q,))
    p.start()
    print q.get()
    p.join()

當子進程正確完成時,沒有問題,並且效果很好,但是當我的子進程在完成之前終止時,問題就開始了。 在這種情況下,我的應用程序正在等待。

q.get()p.join()一個超時並不能完全解決這個問題,因為我想立即知道子進程死了而不是等待超時。

另一個問題是q.get()上的超時會產生異常,我更願意避免這種情況。

有人可以建議我一種更優雅的方法來克服這些問題嗎?

隊列和信號

一種可能性是注冊信號處理程序並使用它來傳遞哨兵值。 在 Unix 上,您可以在父級中處理SIGCHLD ,但這不是您的情況。 根據信號模塊

在 Windows 上,signal() 只能用 SIGABRT、SIGFPE、SIGILL、SIGINT、SIGSEGV、SIGTERM 或 SIGBREAK 調用。

不確定通過任務管理器殺死它是否會轉化為SIGTERM但您可以嘗試一下。

要處理SIGTERM您需要在子SIGTERM中注冊信號處理程序。

import os
import sys
import time
import signal
from functools import partial
from multiprocessing import Process, Queue

SENTINEL = None


def _sigterm_handler(signum, frame, queue):
    print("received SIGTERM")
    queue.put(SENTINEL)
    sys.exit()


def register_sigterm(queue):
    global _sigterm_handler
    _sigterm_handler = partial(_sigterm_handler, queue=queue)
    signal.signal(signal.SIGTERM, _sigterm_handler)


def some_func(q):
    register_sigterm(q)
    print(os.getpid())
    for i in range(30):
        time.sleep(1)
        q.put(f'msg_{i}')


if __name__ == '__main__':

    q = Queue()
    p = Process(target=some_func, args=(q,))
    p.start()
    for msg in iter(q.get, SENTINEL):
        print(msg)
    p.join()

示例輸出:

12273
msg_0
msg_1
msg_2
msg_3
received SIGTERM

Process finished with exit code 0

隊列& Process.is_alive()

即使這適用於任務管理器,您的用例聽起來也不能排除強制殺死,所以我認為您最好采用不依賴信號的方法。

如果您的進程p.is_alive()調用queue.get()並指定timeout並處理Empty異常,則您可以檢查循環:

import os
import time
from queue import Empty
from multiprocessing import Process, Queue

def some_func(q):
    print(os.getpid())
    for i in range(30):
        time.sleep(1)
        q.put(f'msg_{i}')


if __name__ == '__main__':

    q = Queue()
    p = Process(target=some_func, args=(q,))
    p.start()

    while p.is_alive():
        try:
            msg = q.get(timeout=0.1)
        except Empty:
            pass
        else:
            print(msg)

    p.join()

也可以避免異常,但我不建議這樣做,因為您不會將等待時間花在“排隊”上,因此會降低響應速度:

while p.is_alive():
    if not q.empty():
        msg = q.get_nowait()
        print(msg)
        time.sleep(0.1)

管道& Process.is_alive()

如果您打算為每個孩子使用一個連接,則可以使用管道而不是隊列。 它比隊列(安裝在管道頂部)性能更高,您可以使用multiprocessing.connection.wait (Python 3.3+) 一次等待多個對象准備就緒。

multiprocessing.connection.wait(object_list,超時=無)

等到 object_list 中的對象准備就緒。 返回 object_list 中准備好的對象的列表。 如果 timeout 是一個浮點數,那么調用最多會阻塞多少秒。 如果 timeout 為 None ,那么它將無限期地阻塞。 負超時相當於零超時。

對於 Unix 和 Windows,如果對象是可讀的 Connection 對象,則該對象可以出現在 object_list 中; 一個已連接且可讀的 socket.socket 對象; 或 Process 對象的哨兵屬性。 當有數據可供讀取或另一端已關閉時,連接或套接字對象已准備就緒。

Unix : wait(object_list, timeout) 幾乎等同於 select.select(object_list, [], [], timeout)。 不同之處在於,如果 select.select() 被信號中斷,它會引發錯誤號為 EINTR 的 OSError,而 wait() 不會。

Windows :object_list 中的項必須是可等待的整數句柄(根據 Win32 函數 WaitForMultipleObjects() 的文檔使用的定義),或者它可以是具有 fileno() 方法的對象,該方法返回套接字句柄或管柄。 (請注意,管道句柄和套接字句柄不是可等待句柄。)

您可以使用它來同時等待進程的哨兵屬性和管道的父端。

import os
import time
from multiprocessing import Process, Pipe
from multiprocessing.connection import wait


def some_func(conn_write):
    print(os.getpid())
    for i in range(30):
        time.sleep(1)
        conn_write.send(f'msg_{i}')


if __name__ == '__main__':

    conn_read, conn_write = Pipe(duplex=False)
    p = Process(target=some_func, args=(conn_write,))
    p.start()

    while p.is_alive():
        wait([p.sentinel, conn_read])  # block-wait until something gets ready
        if conn_read.poll():  # check if something can be received
            print(conn_read.recv())
    p.join()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM