【问题标题】:abortable sleep() in PythonPython中的可中止睡眠()
【发布时间】:2015-02-12 13:08:25
【问题描述】:

我需要一个可以中止的sleep() 方法(如herehere 所述)。

我的方法是让threading.Event.wait() 在指定的持续时间内超时:

def abortable_sleep(secs, abort_event):
    abort_event.wait(timeout=secs)
    abort_event.clear()

在调用 abortable_sleep(10, _abort) 之后,我现在可以(从另一个线程)调用 _event.set(_abort)abortable_sleep() 在 10 秒之前终止。

例子:

def sleeping_thread():
    _start = time.perf_counter()
    print("%f thread started" % (time.perf_counter() - _start))
    abortable_sleep(5, _abort)
    print("%f thread stopped" % (time.perf_counter() - _start))

if __name__ == '__main__':

    _abort = threading.Event()
    while True:
        threading.Thread(target=sleeping_thread).start()
        time.sleep(3)
        _abort.set()
        time.sleep(1)

输出:

0.000001 thread started
3.002668 thread stopped
0.000002 thread started
3.003014 thread stopped
0.000001 thread started
3.002928 thread stopped
0.000001 thread started

此代码按预期工作,但我仍有一些问题:

  • 难道没有一种更简单的方式来获得某物吗? likea sleep() 可以中止吗?
  • 这可以做到更优雅吗?例如。这样我必须小心Event 实例,它没有绑定到abortable_sleep() 的实例
  • 我是否必须预料到像while True: abortable_sleep(0.0001) 这样的高频循环会出现性能问题? wait()-timeout 是如何实现的?

【问题讨论】:

  • 在我看来这或多或少是正确的方法。这取决于您在调用 _abort 时想要发生什么。你想让所有休眠的线程都被唤醒吗?您想要唤醒其中一个线程吗?你想中止特定的线程吗?根据答案,您可能想要使用不同的模型(条件?),或者您可能想要使用线程本地存储来保存 _abort 事件或子类化 Thread 类以保存 _abort 对象。
  • re:第 1 点 - 可能不是。至于第 2 点 - 您应该继承 Thread 并封装此行为。
  • abortable_sleep(0.0001):这总会有问题,无论是否中止。您要求睡 100 我们,但线程调度程序可能要等很久之后才能唤醒您。
  • 还有竞争条件。如果其他线程在您完成睡眠并清除它之后设置事件,您将遇到问题。你可以在睡觉前打电话给event.clear(),但这只会缩短危险窗口。

标签: python thread-sleep


【解决方案1】:

我有一个包装类,它基本上在Event 之上添加了一些睡眠语义。好消息是你只需要传递一个Sleep 对象,如果你愿意,你可以多次调用sleep()(虽然sleep() 不是线程安全的)并且你可以从另一个线程wake() .

from threading import Event

class Sleep(object):
    def __init__(self, seconds, immediate=True):
        self.seconds = seconds
        self.event = Event()
        if immediate:
            self.sleep()

    def sleep(self, seconds=None):
        if seconds is None:
            seconds = self.seconds
        self.event.clear()
        self.event.wait(timeout=seconds)

    def wake(self):
        self.event.set()

使用示例:

if __name__ == '__main__':
    from threading import Thread
    import time
    import logging

    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(created)d - %(message)s')
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    logger.info("sleep")
    s = Sleep(3)
    logger.info("awake")

    def wake_it(sleeper):
        time.sleep(1)
        logger.info("wakeup!")
        sleeper.wake()

    logger.info("sleeping again")
    s = Sleep(60, immediate=False)
    Thread(target=wake_it, args=[s]).start()
    s.sleep()
    logger.info("awake again")

上面的输出可能是这样的:

1423750549 - sleep
1423750552 - awake
1423750552 - sleeping again
1423750553 - wakeup!
1423750553 - awake again

正是你所做的,但封装在一个类中。

【讨论】:

    【解决方案2】:

    由于竞争条件,您的解决方案并不总是完全正确的。您应该改用threading.BoundedSemaphore()。创建后立即致电aquire()。当你想睡觉时,调用acquire() 超时,然后调用release() 如果acquire() 返回真。要提前中止睡眠,请从不同的线程调用release();如果没有进行睡眠,这将引发ValueError

    如果其他线程在错误的时间调用set()(即在您实际等待事件时以外的任何时间),则使用事件会出现问题。

    【讨论】:

    • 我不确定我是否看到了比赛条件。能举个例子吗?
    • @AndréLaszlo:线程 1 调用 abortable_sleep()。睡眠完成。线程 2 尝试通过设置事件来中止睡眠。线程 1 再次调用 abortable_sleep(),它立即返回,因为事件已经设置。这是一个错误。
    • 啊哈,谢谢。自从我在代码中的 wait() 之前放了一个 clear() 之后,我一定已经考虑过了。它也应该在abortable_sleep 中工作,对吧?假设至少是同一个线程进行清除/等待。
    • 这可能适用于这个特定的用例,但如果你想让其他线程知道它是否成功,你必须使用比事件更复杂的东西。
    【解决方案3】:

    我会将睡眠/中止功能封装在一个新类中:

    class AbortableSleep():
        def __init__(self):
            self._condition = threading.Condition()
    
        def __call__(self, secs):
            with self._condition:
                self._aborted = False
                self._condition.wait(timeout=secs)
                return not self._aborted
    
        def abort(self):
            with self._condition:
                self._condition.notify()
                self._aborted = True
    

    然后我还会提供一个Thread 子类来管理基于每个线程的唤醒例程的共享:

    class ThreadWithWakeup(threading.Thread):
        def __init__(self, *args, **kwargs):
            self.abortable_sleep = AbortableSleep()
            super(ThreadWithWakeup, self).__init__(*args, **kwargs)
    
        def wakeup(self):
            self.abortable_sleep.abort()
    

    任何其他有权访问此线程的线程都可以调用wakeup() 来中止当前的abortable_sleep()(如果正在进行中)。


    使用 ThreadWithWakeup

    您可以使用ThreadWithWakeup 类创建线程,并像这样使用它:

    class MyThread(ThreadWithWakeup):
        def run(self):
            print "Sleeper: sleeping for 10"
            if self.abortable_sleep(10):
                print "Sleeper: awoke naturally"
            else:
                print "Sleeper: rudely awoken"
    
    t = MyThread()
    t.start()
    print "Main: sleeping for 5"
    for i in range(5):
        time.sleep(1)
        print i + 1 
    print "Main: waking thread"
    t.wakeup()
    

    输出如下:

    Sleeper: sleeping for 10
    Main: sleeping for 5
    1
    2
    3
    4
    5
    Main: waking thread
    Sleeper: rudely awoken
    

    单独使用 AbortableSleep

    您也可以单独使用AbortableSleep 类,如果您由于某种原因不能使用ThreadWithWakeup 类,这很方便(也许您在主线程中,也许其他东西会创建线程你等):

    abortable_sleep = AbortableSleep()
    def run():
        print "Sleeper: sleeping for 10"
        if abortable_sleep(10):
            print "Sleeper: awoke naturally"
        else:
            print "Sleeper: rudely awoken"
    threading.Thread(target=run).start()
    
    print "Main: sleeping for 5"
    for i in range(5):
        time.sleep(1)
        print i + 1
    print "Main: aborting"
    abortable_sleep.abort()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-02
      • 2015-06-28
      相关资源
      最近更新 更多