Python多处理子进程中的忽略SIGINT

2024-05-17 13:36:50 发布

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

当我在OSX或Linux上运行以下代码,然后按ctrl+c时,会启动一个“优雅关闭”。看起来像这样:

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

但是,当我在Windows10机器上运行一些代码时,keyboardInterrupt会在self.event.wait()行中引发,从而阻止正常关闭。我尝试了不同的方法,如here所述,以防止子进程接收到信号。在

使用Python2.7在不同的操作系统上获得相同行为的正确方法是什么?在

^{pr2}$

Tags: 方法代码pytestlinuxsubprocessctrlosx
2条回答

使用来自pywin32win32api.SetConsoleCtrlHandler可以控制窗口如何中断。使用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()

在Windows上,SIGINT是使用CTRL_C_EVENT的控制台控制事件处理程序实现的。子进程继承的是控制台状态,而不是CRT的信号处理状态。因此,如果希望子进程忽略Ctrl+C,则需要在创建子进程之前首先调用^{}来忽略父进程中的Ctrl+C

有一个陷阱。Python在Windows上不使用警报等待,比如processjoin方法中的wait。因为它在主线程上分派信号处理程序,所以主线程在join()中被阻塞这一事实意味着永远不会调用信号处理程序。必须用time.sleep()上的一个循环来替换join,这是可以由Ctrl+C中断的,因为它在内部等待一个Windows事件,并设置自己的控制处理程序来设置这个事件。或者您可以使用您自己的异步控制处理程序集通过ctypes。下面的示例实现了这两种方法,并且在Python2和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()

相关问题 更多 >