簡體   English   中英

在Python多處理子進程中忽略SIGINT

[英]Ignore SIGINT in Python multiprocessing Subprocess

當我在OSX或Linux上運行以下代碼,然后按ctrl+c將啟動“正常關機”。 看起來像這樣:

$ python subprocess_test.py    
Subprocess:  <MyProcess(MyProcess-1, started)>
^CMain: Graceful shutdown
Subprocess: shutdown

但是,當我在Windows10機器上運行一些代碼時,在self.event.wait()行中引發了KeyboardInterruptself.event.wait()阻止了正常關機。 我嘗試了此處所述的其他方法來防止子進程接收信號。

使用Python 2.7在不同的操作系統上獲得相同行為的正確方法是什么?

import multiprocessing
import signal

class MyProcess(multiprocessing.Process):

    def __init__(self):
        super(MyProcess, self).__init__()
        self.event = multiprocessing.Event()

    def run(self):
        print "Subprocess: ", multiprocessing.current_process()
        self.event.wait()
        print "Subprocess: shutdown"


def sighandler(a,b,):
    print "Main: Graceful shutdown"
    p1.event.set()

def run():
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    global p1
    p1 = MyProcess()

    p1.start()

    signal.signal(signal.SIGINT, sighandler)
    p1.join()


if __name__ == '__main__':
    run()

在Windows上, SIGINT是使用CTRL_C_EVENT的控制台控件事件處理程序實現的。 由子進程繼承的是控制台狀態,而不是CRT的信號處理狀態。 因此,如果SetConsoleCtrlHandler忽略Ctrl + C,則需要先在父進程中調用SetConsoleCtrlHandler以忽略Ctrl + C。

有一個陷阱。 Python在Windows上不使用可警告的等待,例如進程join方法中的等待。 由於它在主線程上調度信號處理程序,因此主線程在join()被阻塞的事實意味着您的信號處理程序將永遠不會被調用。 您必須將連接替換為time.sleep()上的循環,該循環可被Ctrl + C中斷,因為它在內部等待Windows事件並設置自己的控件處理程序來設置此事件。 或者,您也可以通過ctypes使用自己的異步控制處理程序集。 以下示例實現了這兩種方法,並且可以在Python 2和3中使用。

import sys
import signal
import multiprocessing

if sys.platform == "win32":
    # Handle Ctrl+C in the Windows Console
    import time
    import errno
    import ctypes
    import threading

    from ctypes import wintypes

    kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)

    PHANDLER_ROUTINE = ctypes.WINFUNCTYPE(
        wintypes.BOOL,
        wintypes.DWORD) # _In_ dwCtrlType

    win_ignore_ctrl_c = PHANDLER_ROUTINE() # alias for NULL handler

    def _errcheck_bool(result, func, args):
        if not result:
            raise ctypes.WinError(ctypes.get_last_error())
        return args

    kernel32.SetConsoleCtrlHandler.errcheck = _errcheck_bool
    kernel32.SetConsoleCtrlHandler.argtypes = (
        PHANDLER_ROUTINE, # _In_opt_ HandlerRoutine
        wintypes.BOOL)    # _In_     Add

class MyProcess(multiprocessing.Process):

    def __init__(self):
        super(MyProcess, self).__init__()
        self.event = multiprocessing.Event()

    def run(self):
        print("Subprocess: %r" % multiprocessing.current_process())
        self.event.wait()
        print("Subprocess: shutdown")

    if sys.platform == "win32":
        def join(self, timeout=None):
            if threading.current_thread().name != "MainThread":
                super(MyProcess, self).join(timeout)
            else:
                # use time.sleep to allow the main thread to
                # interruptible by Ctrl+C
                interval = 1
                remaining = timeout
                while self.is_alive():
                    if timeout is not None:
                        if remaining <= 0:
                            break
                        if remaining < interval:
                            interval = remaining
                            remaining = 0
                        else:
                            remaining -= interval
                    try:
                        time.sleep(interval)
                    except IOError as e:
                        if e.errno != errno.EINTR:
                            raise
                        break

def run():
    p1 = MyProcess()

    # Ignore Ctrl+C, which is inherited by the child process.
    if sys.platform == "win32":
         kernel32.SetConsoleCtrlHandler(win_ignore_ctrl_c, True)
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    p1.start()

    # Set a Ctrl+C handler to signal graceful shutdown.
    if sys.platform == "win32":
        kernel32.SetConsoleCtrlHandler(win_ignore_ctrl_c, False)
        # comment out the following to rely on sig_handler
        # instead. Note that using the normal sig_handler requires
        # joining using a loop on time.sleep() instead of the
        # normal process join method. See the join() method
        # defined above.
        @PHANDLER_ROUTINE
        def win_ctrl_handler(dwCtrlType):
            if (dwCtrlType == signal.CTRL_C_EVENT and
                not p1.event.is_set()):
                print("Main <win_ctrl_handler>: Graceful shutdown")
                p1.event.set()
            return False

        kernel32.SetConsoleCtrlHandler(win_ctrl_handler, True)

    def sig_handler(signum, frame):
        if not p1.event.is_set():
            print("Main <sig_handler>: Graceful shutdown")
            p1.event.set()

    signal.signal(signal.SIGINT, sig_handler)
    p1.join()

if __name__ == "__main__":
    run()

使用pywin32中的 win32api.SetConsoleCtrlHandler可以控制Windows如何中斷。 使用SetConsoleCtrlHandler(None, True)會導致調用過程忽略CTRL + C輸入。 使用SetConsoleCtrlHandler(sighandler, True)可以注冊特定的處理程序。

綜上所述,解決問題的方法如下:

import multiprocessing
import signal
import sys

class MyProcess(multiprocessing.Process):

    def __init__(self):
        super(MyProcess, self).__init__()
        self.event = multiprocessing.Event()

    def run(self):
        if sys.platform == "win32":
            import win32api # ignoring the signal
            win32api.SetConsoleCtrlHandler(None, True)

        print "Subprocess: ", multiprocessing.current_process()
        self.event.wait()
        print "Subprocess: shutdown"


def sighandler(a,b=None):
    print "Main: Graceful shutdown"
    p1.event.set()

def run():
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    global p1
    p1 = MyProcess()

    p1.start()

    if sys.platform == "win32":
        import win32api
        win32api.SetConsoleCtrlHandler(sighandler, True)
    else:
        signal.signal(signal.SIGINT, sighandler)

    p1.join()


if __name__ == '__main__':
    run()

暫無
暫無

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

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