我使用以下代码来处理SIGINT事件。代码设置了一个多处理事件,以便唤醒"唤醒"正在等待的主线程。
import multiprocessing
import signal
class Class1(object):
_stop_event = multiprocessing.Event()
@staticmethod
def interrupt():
Class1._stop_event.set()
def wait(self):
print("Waiting for _stop_event")
if Class1._stop_event.wait(5):
print("_stop_event set.")
else:
print("Timed out.")
def stop(signum, frame):
print("Received SIG")
Class1.interrupt()
signal.signal(signal.SIGINT, stop)
c = Class1()
c.wait()
没有任何信号,等待方法在10秒后超时,并且进程按预期退出以下输出:
Waiting for _stop_event
Timed out.
发送SIGINT信号时,信号会被处理,但event.wait方法不会立即返回,也不会在超时后返回。进程从不退出。输出是:
Waiting for _stop_event
^CReceived SIG
我可以继续发送SIGINT。该过程不会退出,输出为:
Waiting for _stop_event
^CReceived SIG
^CReceived SIG
^CReceived SIG
^CReceived SIG
....
如果我用检查event.is_set替换Class1.wait方法,那么一切都按预期工作:
def wait(self):
print("Waiting for _stop_event")
while True:
if Class1._stop_event.is_set():
print("_stop_event set.")
break
进程退出,输出为:
Waiting for _stop_event
^CReceived SIG
_stop_event set.
设置事件后如何使event.wait返回? 等待方法甚至不再超时的原因是什么?
答案 0 :(得分:3)
信号仅在主线程上处理。如果主线程在系统调用中被阻塞,则该系统调用将引发InterruptedError。
来自Python文档:
它们[信号]只能出现在Python的“原子”指令之间 解释
例如,time.sleep会引发InterruptedError。似乎event.wait方法没有正确处理这种情况。它不会引发InterruptedError,而只是开始挂起。对我来说,这看起来像Python中的一个错误?
更新:
我把它缩小到multiprocessing.Event中的死锁。如果主线程正在等待一个事件,同时,一个信号在被中断的主线程上设置该事件,那么multiprocessing.event.set()和multiprocessing.event.wait()方法会相互死锁。
此外,该行为依赖于平台。例如。 time.sleep()会在Windows上引发InterruptedError,但只是在Linux上返回。
一个非常笨重的解决方法是保持主线程处理信号。
import multiprocessing
import signal
import threading
import time
class Class1(object):
_stop_event = multiprocessing.Event()
@staticmethod
def interrupt():
Class1._stop_event.set()
def wait_timeout(self):
print("Waiting for _stop_event")
if Class1._stop_event.wait(30):
print("_stop_event set.")
else:
print("Timeout")
def stop(signum, frame):
print("Received SIG")
Class1.interrupt()
exit_event.set()
def run():
c = Class1()
c.wait_timeout()
t = threading.Thread(target=run)
t.daemon = False
t.start()
exit_event = multiprocessing.Event()
signal.signal(signal.SIGINT, stop)
while not exit_event.is_set():
# Keep a main thread around to handle the signal and
# that thread must not be in a event.wait()!
try:
time.sleep(500)
except InterruptedError:
# We were interrupted by the incoming signal.
# Let the signal handler stop the process gracefully.
pass
这很难看他妈的。有人请提供更优雅的解决方案......
答案 1 :(得分:2)
您也可以使用signal模块的pause()
功能代替Event().wait()
。 signal.pause()
会一直睡眠,直到进程收到信号。在这种情况下,当收到SIGINT时,signal.pause()
退出,不返回任何内容。请注意,根据文档,该功能在Windows上不起作用。我在Linux上试过它,它对我有用。
我在此SO thread中遇到了此解决方案。
答案 2 :(得分:1)
你们会喜欢这个。使用threading.Event
,而不是multiprocessing.Event
。然后当你按下^C
时,就会调用信号处理程序!
import threading
import signal
class Class1(object):
_stop_event = threading.Event()
@staticmethod
def interrupt():
Class1._stop_event.set()
def wait(self):
print("Waiting for _stop_event")
if Class1._stop_event.wait(5):
print("_stop_event set.")
else:
print("Timed out.")
def stop(signum, frame):
print("Received SIG")
Class1.interrupt()
signal.signal(signal.SIGINT, stop)
c = Class1()
c.wait()
Waiting for _stop_event
^CReceived SIG
_stop_event set.
答案 3 :(得分:0)
基于Alex的出色研究,这意味着从另一个线程设置标志不会导致死锁。如果更改了中断方法,则原始代码将起作用:
from threading import Thread
(...)
@staticmethod
def interrupt():
Thread(target=Class1._stop_event.set).start()