Python time.sleep()与event.wait()


72

我想在我的多线程Python应用程序中定期执行操作。我已经看到了两种不同的方法

exit = False
def thread_func(): 
    while not exit:
       action()
       time.sleep(DELAY)

要么

exit_flag = threading.Event()
def thread_func(): 
    while not exit_flag.wait(timeout=DELAY):
       action()

一种方法比另一种方法有优势吗?是使用更少的资源还是与其他线程和GIL更好地协作?哪一个使我的应用程序中的其余线程更具响应性?

(假设有一些外部事件集exitexit_flag,我愿意在关闭时等待完整的延迟)


2
设置exit标志的代码在哪里?是在action()调用中还是在另一个线程中,或者可能是由信号处理程序调用的?
tdelaney 2015年

Event.wait在这种情况下使用,即使python 2.x在后台轮询。以1秒为间隔的睡眠时间可以合理地做出反应,并且不那么刺耳。
tdelaney

首先,这将浪费一些CPU时间。
user253751

Event.wait的有趣副作用。我正在反向工程应用程序的python API,该应用程序具有嵌入式python 2.5解释器(ableton live),并且父进程在某种程度上不喜欢python线程,也许它仅在处理事件时运行,从而使rconsole我注射无反应。如果我循环经过time.sleep,它仍然没有响应,但是如果我在主线程中使用event.wait并带有超时,则父应用程序仍在响应,而rconsole仍在响应。
森特2015年

Answers:


69

使用exit_flag.wait(timeout=DELAY)将具有更高的响应速度,因为在exit_flag设置时,您将立即退出while循环。使用time.sleep,即使在设置了事件之后,您也将在time.sleep通话中等待直到睡了DELAY几秒钟。

在实现方面,Python 2.x和Python 3.x具有非常不同的行为。在Python 2.xEvent.wait中,使用大量的小time.sleep调用在纯Python中实现:

from time import time as _time, sleep as _sleep

....
# This is inside the Condition class (Event.wait calls Condition.wait).
def wait(self, timeout=None):
    if not self._is_owned():
        raise RuntimeError("cannot wait on un-acquired lock")
    waiter = _allocate_lock()
    waiter.acquire()
    self.__waiters.append(waiter)
    saved_state = self._release_save()
    try:    # restore state no matter what (e.g., KeyboardInterrupt)
        if timeout is None:
            waiter.acquire()
            if __debug__:
                self._note("%s.wait(): got it", self)
        else:
            # Balancing act:  We can't afford a pure busy loop, so we
            # have to sleep; but if we sleep the whole timeout time,
            # we'll be unresponsive.  The scheme here sleeps very
            # little at first, longer as time goes on, but never longer
            # than 20 times per second (or the timeout time remaining).
            endtime = _time() + timeout
            delay = 0.0005 # 500 us -> initial delay of 1 ms
            while True:
                gotit = waiter.acquire(0)
                if gotit:
                    break
                remaining = endtime - _time()
                if remaining <= 0:
                    break
                delay = min(delay * 2, remaining, .05)
                _sleep(delay)
            if not gotit:
                if __debug__:
                    self._note("%s.wait(%s): timed out", self, timeout)
                try:
                    self.__waiters.remove(waiter)
                except ValueError:
                    pass
            else:
                if __debug__:
                    self._note("%s.wait(%s): got it", self, timeout)
    finally:
        self._acquire_restore(saved_state)

实际上,这意味着使用wait它可能比仅DELAY无条件地休眠整个服务器要消耗更多的CPU ,但这样做的好处是(DELAY响应时间长很多,取决于时间长短)。这也意味着需要经常重新获取GIL,以便可以安排下次睡眠,同时time.sleep可以释放GIL的全部时间。DELAY。现在,更频繁地获取GIL是否会对您应用程序中的其他线程产生明显影响?也许不是。这取决于正在运行的其他线程数以及它们承担的工作量。我的猜测是,除非您有大量的线程,或者另一个线程正在执行大量CPU限制的工作,否则它不会特别引人注目,但是它很容易同时尝试和观察。

在Python 3.x中,大部分实现都移至纯C代码:

import _thread # C-module
_allocate_lock = _thread.allocate_lock

class Condition:
    ...
    def wait(self, timeout=None):
        if not self._is_owned():
            raise RuntimeError("cannot wait on un-acquired lock")
        waiter = _allocate_lock()
        waiter.acquire()
        self._waiters.append(waiter)
        saved_state = self._release_save()
        gotit = False
        try:    # restore state no matter what (e.g., KeyboardInterrupt)
            if timeout is None:
                waiter.acquire()
                gotit = True
            else:
                if timeout > 0:
                    gotit = waiter.acquire(True, timeout)  # This calls C code
                else:
                    gotit = waiter.acquire(False)
            return gotit
        finally:
            self._acquire_restore(saved_state)
            if not gotit:
                try:
                    self._waiters.remove(waiter)
                except ValueError:
                    pass

class Event:
    def __init__(self):
        self._cond = Condition(Lock())
        self._flag = False

    def wait(self, timeout=None):
        self._cond.acquire()
        try:
            signaled = self._flag
            if not signaled:
                signaled = self._cond.wait(timeout)
            return signaled
        finally:
            self._cond.release()

和获得锁的C代码:

该实现是响应式的,不需要频繁唤醒即可重新获取GIL,因此您可以两全其美。


2
因此,这是否意味着sleep(DELAY)GIL较轻?尽管不那么准确?
user3012759 2015年

@ user3012759我会这样认为,因为内部的每次唤醒wait都需要重新获取GIL,在那里sleep可以将其全部释放DELAY
dano 2015年

3
这是python 2.x(在3.x中要好得多),并且非常糟糕,尤其是随着线程数的增加。
tdelaney 2015年

@tdelaney 3.x impl看起来如何?
user3012759 2015年

1
@tdelaney是的,很好。在Python 3.x中,等待是在C中实现的,并释放整个等待的GIL。我将更新答案以显示代码
dano

9

Python 2. *
就像@dano所说的那样,event.wait的响应速度更快,
但是当系统时间向后更改时,它可能会很危险错误#1607041:由于时钟更改,Condition.wait超时失败

看到这个例子:

def someHandler():
   while not exit_flag.wait(timeout=0.100):
       action()

通常action()会在100ms间隔内调用。
但是,当您更改时间时。一个小时,则两个动作之间要暂停一小时。

结论:如果可以更改时间,则应避免 event.wait


3

有趣的是,可以单独调用event.wait()方法:

from threading import Event # Needed for the  wait() method
from time import sleep     

print("\n Live long and prosper!")
sleep(1)               # Conventional sleep() Method.
print("\n Just let that soak in..")   
Event().wait(3.0) # wait() Method, useable sans thread.
print("\n Make it So! = )\n")

那么,为什么在多线程之外不使用wait()替代sleep()?一句话,禅。(当然。)代码的清晰是很重要的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.