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

时间:2014-06-26 04:00:00

标签: python python-multithreading

我使用以下代码来处理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返回? 等待方法甚至不再超时的原因是什么?

4 个答案:

答案 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()