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

Posted

技术标签:

【中文标题】Python time.sleep() 与 event.wait()【英文标题】:Python time.sleep() vs event.wait() 【发布时间】:2015-05-18 21:19:35 【问题描述】:

我想在我的多线程 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,我愿意在关机时等待完全延迟)

【问题讨论】:

设置exit 标志的代码在哪里?它是在action() 调用中还是在另一个线程中,或者可能被信号处理程序调用? 我在这种情况下使用Event.wait,即使python 2.x 在后台轮询。例如,以 1 秒为间隔睡觉是相当有反应的,而且干扰较少。 第一个会浪费一些 CPU 时间,一方面。 Event.wait 的有趣副作用。我正在对具有嵌入式 python 2.5 解释器(ableton live)的应用程序的 python API 进行反向工程,并且父进程在某种程度上不喜欢 python 线程,也许它只在处理事件时运行,使 rconsole我注射反应迟钝。如果我在 time.sleep 上循环,它仍然没有响应,但如果我在主线程中使用 event.wait 并超时,父应用程序仍在响应并且 rconsole 响应合理。 【参考方案1】:

使用exit_flag.wait(timeout=DELAY) 会更灵敏,因为当设置exit_flag 时,您会立即跳出while 循环。使用time.sleep,即使在设置了事件之后,您也会在time.sleep 调用中等待,直到您睡了DELAY 秒。

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

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代码:

/* Helper to acquire an interruptible lock with a timeout.  If the lock acquire
 * is interrupted, signal handlers are run, and if they raise an exception,
 * PY_LOCK_INTR is returned.  Otherwise, PY_LOCK_ACQUIRED or PY_LOCK_FAILURE
 * are returned, depending on whether the lock can be acquired withing the
 * timeout.
 */
static PyLockStatus
acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds)

    PyLockStatus r;
    _PyTime_timeval curtime;
    _PyTime_timeval endtime;


    if (microseconds > 0) 
        _PyTime_gettimeofday(&endtime);
        endtime.tv_sec += microseconds / (1000 * 1000);
        endtime.tv_usec += microseconds % (1000 * 1000);
    


    do 
        /* first a simple non-blocking try without releasing the GIL */
        r = PyThread_acquire_lock_timed(lock, 0, 0);
        if (r == PY_LOCK_FAILURE && microseconds != 0) 
            Py_BEGIN_ALLOW_THREADS  // GIL is released here
            r = PyThread_acquire_lock_timed(lock, microseconds, 1);
            Py_END_ALLOW_THREADS
        

        if (r == PY_LOCK_INTR) 
            /* Run signal handlers if we were interrupted.  Propagate
             * exceptions from signal handlers, such as KeyboardInterrupt, by
             * passing up PY_LOCK_INTR.  */
            if (Py_MakePendingCalls() < 0) 
                return PY_LOCK_INTR;
            

            /* If we're using a timeout, recompute the timeout after processing
             * signals, since those can take time.  */
            if (microseconds > 0) 
                _PyTime_gettimeofday(&curtime);
                microseconds = ((endtime.tv_sec - curtime.tv_sec) * 1000000 +
                                (endtime.tv_usec - curtime.tv_usec));

                /* Check for negative values, since those mean block forever.
                 */
                if (microseconds <= 0) 
                    r = PY_LOCK_FAILURE;
                
            
        
     while (r == PY_LOCK_INTR);  /* Retry if we were interrupted. */

    return r;

此实现具有响应性,不需要频繁唤醒以重新获取 GIL,因此您可以两全其美。

【讨论】:

那么,这是否意味着sleep(DELAY) 的 GIL 较轻?虽然不准确? @user3012759 我会这么认为,因为wait 内部的每次唤醒都需要重新获取 GIL,而 sleep 可以在整个 DELAY 中释放它。 这是 python 2.x(它在 3.x 中要好得多)而且它非常糟糕,尤其是随着线程数量的增加。 @tdelaney 3.x impl 是什么样子的? @tdelaney 是的,好点。在 Python 3.x 中,等待是用 C 实现的,并为整个等待释放 GIL。我将更新我的答案以显示代码【参考方案2】:

Python 2.* 就像@dano 所说,event.wait 响应速度更快, 但是当系统时间向后更改时,它可能很危险,而它正在等待!bug# 1607041: Condition.wait timeout fails on clock change

查看此示例:

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

通常action() 将在 100 毫秒内调用。 但是当你改变时间前。一小时,然后在两个动作之间暂停一小时。

结论:在时间可以改变的情况下,你应该避免event.wait,这可能是灾难性的!

Python 3使用单调时钟来实现超时,所以在那里解决了

【讨论】:

这真的很有趣 - 但如果 Python 2.*受到影响,最好澄清一下 @lys 已在 3.* 中修复,我更新了答案【参考方案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() 的替代方法呢?一句话,禅。 (当然。)代码的清晰性很重要。

【讨论】:

【参考方案4】:

根据我的经验,使用 time.sleep() 会占用 CPU 并使应用程序滞后,这是因为 sleep 函数是其他线程的阻塞方法,而 Event.wait() 是非阻塞方法其他线程。

您可以通过查看线程等待释放锁所需的时间来了解这一点!

此外,如果您不知道阻塞线程所需的时间,Event.wait() 将非常有用!这样您就可以setclear 事件

【讨论】:

不,sleep 不会阻塞其他线程,它只会阻塞它正在运行的线程。 @FahadAlduraibi 我知道它只会阻塞它正在运行的线程,但是基于 GIL GLOBAL INTERPRETER LOCK,它会严重影响其他正在运行的线程!因为 sleep 方法会消耗资源,而 wait 不会

以上是关于Python time.sleep() 与 event.wait()的主要内容,如果未能解决你的问题,请参考以下文章

python函数深入浅出 16.time.sleep()函数详解

python的time.sleep()有多准确?

Python - time.sleep 的替代品

吴裕雄 python深度学习与实践

Python - 准确的 time.sleep

Python 的 time.sleep()