Python multiprocessing.Event.wait在被信号中断时挂起

Python multiprocessing.Event.wait在被信号中断时挂起,python,python-multithreading,Python,Python Multithreading,我使用以下代码来处理SIGINT事件。代码将multiprocessing.event设置为“唤醒”正在等待的主线程 import multiprocessing import signal class Class1(object): _stop_event = multiprocessing.Event() @staticmethod def interrupt(): Class1._stop_event.set() def wait(se

我使用以下代码来处理SIGINT事件。代码将multiprocessing.event设置为“唤醒”正在等待的主线程

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
Waiting for _stop_event
^CReceived SIG
^CReceived SIG
^CReceived SIG
^CReceived SIG
....
Waiting for _stop_event
^CReceived SIG
_stop_event set.
我可以继续发送信号。进程不会退出,输出为:

Waiting for _stop_event
^CReceived SIG
Waiting for _stop_event
^CReceived SIG
^CReceived SIG
^CReceived SIG
^CReceived SIG
....
Waiting for _stop_event
^CReceived SIG
_stop_event set.
如果我将Class1.wait方法替换为event.is_set的检查,则一切正常:

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
Waiting for _stop_event
^CReceived SIG
^CReceived SIG
^CReceived SIG
^CReceived SIG
....
Waiting for _stop_event
^CReceived SIG
_stop_event set.
设置事件后如何使event.wait返回?
wait方法不再超时的原因是什么?

信号只在主线程上处理。如果主线程在系统调用中被阻塞,则该系统调用将引发InterruptedError

从Python文档中:

它们[信号]只能出现在Python的“原子”指令之间 翻译

例如,time.sleep会引发中断错误。似乎event.wait方法无法正确处理此场景。它不会引发中断错误,只是开始挂起。这看起来像Python中的一个bug


更新:

我将其缩小为multiprocessing.Event中的死锁。如果主线程正在等待一个事件,同时,一个信号会在中断的主线程上设置该事件,则multiprocessing.event.set()和multiprocessing.event.wait()方法会互相死锁

此外,该行为严重依赖于平台。例如,time.sleep()会在Windows上引发中断错误,但在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

这真是他妈的丑。有人请提供一个更优雅的解决方案……

你们会喜欢这个的。使用
threading.Event
,而不是
多处理.Event
。然后,当您按
^C
时,信号处理程序将被调用,就像它应该被调用一样

来源 输出
您也可以使用模块的
pause()
函数代替
Event().wait()
<代码>信号。暂停()休眠,直到进程接收到信号。在这种情况下,当接收到SIGINT时,
signal.pause()
退出,不返回任何内容。请注意,根据文档,此功能在Windows上不起作用。我在Linux上试过,它对我很有效


我在这篇文章中遇到了这个解决方案。

基于Alex出色的调查,这意味着从另一个线程设置标志不会导致死锁。如果中断方法更改如下,则原始代码将起作用:

from threading import Thread

(...)
    @staticmethod
    def interrupt():
        Thread(target=Class1._stop_event.set).start()

美好的在Linux上对我来说很有魅力。但是,没有在Windows上的“模拟”SIGINT上设置事件。对我来说,解决问题的方法是使用
多处理.event
而不是退出事件。is\u set()
而不是使用
退出事件.wait()
。谢谢您还可以将
exit\u event.set()
委托给另一个线程,而不是调用它并创建死锁。基本上,信号回调变为:
Thread(target=exit\u event.set).start()