【问题标题】:python asyncronous thread exception handlingpython异步线程异常处理
【发布时间】:2013-11-27 05:15:35
【问题描述】:

我正在尝试在 Python 中实现超时功能。

它通过使用函数装饰器包装函数来工作,该装饰器将函数作为线程调用,但也调用“看门狗”线程,该线程将在指定时间段后在函数线程中引发异常。

它目前适用于不休眠的线程。在do_rand 调用期间,我怀疑“异步”异常实际上是在time.sleep 调用之后和执行超出try/except 块之后调用的,因为这可以解释Unhandled exception in thread started by 错误。此外,来自do_rand 调用的错误是在调用后 7 秒(time.sleep 的持续时间)生成的。

我将如何“唤醒”一个线程(使用 ctypes?)以使其响应异步异常?

或者可能完全不同的方法?

代码:

# Import System libraries
import ctypes
import random
import sys
import threading
import time

class TimeoutException(Exception):
    pass

def terminate_thread(thread, exc_type = SystemExit):
    """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
    """
    if not thread.isAlive():
        return

    exc = ctypes.py_object(exc_type)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread.ident), exc)

    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class timeout_thread(threading.Thread):
    def __init__(self, interval, target_thread):
        super(timeout_thread, self).__init__()
        self.interval     = interval
        self.target_thread = target_thread
        self.done_event = threading.Event()
        self.done_event.clear()

    def run(self):
        timeout = not self.done_event.wait(self.interval)
        if timeout:
            terminate_thread(self.target_thread, TimeoutException)

class timeout_wrapper(object):
    def __init__(self, interval = 300):
        self.interval = interval

    def __call__(self, f):
        def wrap_func(*args, **kwargs):
            thread = threading.Thread(target = f, args = args, kwargs = kwargs)
            thread.setDaemon(True)
            timeout_ticker = timeout_thread(self.interval, thread)
            timeout_ticker.setDaemon(True)
            timeout_ticker.start()
            thread.start()
            thread.join()
            timeout_ticker.done_event.set()
        return wrap_func

@timeout_wrapper(2)
def print_guvnah():
    try:
        while True:
            print "guvnah"

    except TimeoutException:
        print "blimey"

def print_hello():
    try:
        while True:
            print "hello"

    except TimeoutException:
        print "Whoops, looks like I timed out"

def do_rand(*args):
    try:
        rand_num   = 7 #random.randint(0, 10)
        rand_pause = 7 #random.randint(0,  5)
        print "Got rand: %d" % rand_num
        print "Waiting for %d seconds" % rand_pause
        time.sleep(rand_pause)
    except TimeoutException:
        print "Waited too long"

print_guvnah()
timeout_wrapper(3)(print_hello)()
timeout_wrapper(2)(do_rand)()

【问题讨论】:

    标签: python multithreading exception asynchronous ctypes


    【解决方案1】:

    问题在于time.sleep 阻塞。而且它真的很难阻塞,所以唯一能真正中断它的是进程信号。但是带有它的代码变得非常混乱,在某些情况下甚至信号都不起作用(例如,当您正在阻止 socket.recv() 时,请参见:recv() is not interrupted by a signal in multithreaded environment)。

    因此通常无法中断线程(不杀死整个进程)(更不用说有人可以简单地覆盖线程中的信号处理)。

    但在这种特殊情况下,您可以使用线程模块中的Event 类,而不是使用time.sleep

    线程 1

    from threading import Event
    
    ev = Event()
    ev.clear()
    
    state = ev.wait(rand_pause) # this blocks until timeout or .set() call
    

    线程 2(确保它可以访问相同的 ev 实例)

    ev.set() # this will unlock .wait above
    

    请注意,state 将是事件的内部状态。因此state == True 将意味着它被.set() 解锁,而state == False 将意味着发生超时。

    在此处阅读有关活动的更多信息:

    http://docs.python.org/2/library/threading.html#event-objects

    【讨论】:

    • 感谢您的回复,但是,此代码的重点是成为一个库(希望如此)。因此,我真的不想限制用户代码以排除使用 time.sleep 或任何其他阻塞功能。 do_rand 只是一个测试。我希望也许我可以使用 ctypes(或类似的)修改 PyThreadState:gist.github.com/gdementen/….
    • @dilbert 正如我所说:recv() 之类的系统调用可能(并且会在阻塞recv() 的情况下)无限期地锁定您的线程。如果不(强制)终止整个过程,您将无法中断它。因此,我认为您会走得太远,问题无法解决。但是你应该问的问题是:这首先是一个问题吗?也许允许阻塞整个线程一点也不坏?
    • 在Windows上,只能从time.sleep中断主线程。它在事件上使用WaitForSingleObject,而所有其他线程使用不间断的Sleep。 POSIX 系统上time.sleep 使用的select 调用可以使用pthread_kill 来中断,以定位线程。
    • @freakish,这是一个公平的观点。这段代码的想法是终止长期运行的任务,这些任务违反了开发人员对执行时间的期望。
    • @eryksun,一方面,感谢您提供的信息,但另一方面,这非常令人痛苦(关于 Windows 非主线程)。
    【解决方案2】:

    您需要使用睡眠以外的其他东西,或者您需要向其他线程发送信号以使其唤醒。

    我使用的一个选项是设置一对文件描述符并使用 select 或 poll 代替睡眠,这使您可以向文件描述符写入一些内容以唤醒另一个线程。或者,如果您需要的只是操作出错,您只需等待睡眠结束,因为它花费了太长时间并且没有其他依赖它。

    【讨论】:

    • 我将如何向线程发送信号?哪个信号?
    • signal 模块会做到这一点。不过,文件描述符方法可能更可靠。
    • 我非常希望避免使用文件。所以信号没有什么特别之处,比如,任何人都可以吗?
    • 好吧,我会避免使用 SIGTERM 和 SIGKILL。 =) 您可能需要添加一个信号处理程序以避免信号杀死您的进程。您也依赖于信号传递来中断您期望的线程上的睡眠。这可能不适用于所有操作系统。
    • 您的问题是睡眠停止线程运行,因此在它退出睡眠之前它看不到引发异常的请求。如果你想要一个可中断的睡眠,你需要使用我提到的选择/轮询机制,或者你可以考虑使用条件变量等。请参阅threading 模块。
    猜你喜欢
    • 2013-09-10
    • 2022-10-14
    • 2022-07-07
    • 1970-01-01
    • 1970-01-01
    • 2013-02-27
    • 2019-08-29
    • 2017-10-24
    • 1970-01-01
    相关资源
    最近更新 更多