[英]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()
行中引發了KeyboardInterrupt
, self.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.