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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29082268/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 04:03:36  来源:igfitidea点击:

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

pythonmultithreadingsleep

提问by AShelly

I want to perform an action at a regular interval in my multi-threaded Python application. I have seen two different ways of doing it

我想在我的多线程 Python 应用程序中定期执行一个操作。我见过两种不同的方法

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

or

或者

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

Is there an advantage to one way over the other? Does one use less resources, or play nicer with other threads and the GIL? Which one makes the remaining threads in my app more responsive?

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

(Assume some external event sets exitor exit_flag, and I am willing to wait the full delay while shutting down)

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

采纳答案by dano

Using exit_flag.wait(timeout=DELAY)will be more responsive, because you'll break out of the while loop instantly when exit_flagis set. With time.sleep, even after the event is set, you're going to wait around in the time.sleepcall until you've slept for DELAYseconds.

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

In terms of implementation, Python 2.x and Python 3.x have very different behavior. In Python 2.x Event.waitis implemented in pure Python using a bunch of small time.sleepcalls:

在实现方面,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)

This actually means using waitis probably a bit more CPU-hungry than just sleeping the full DELAYunconditionally, but has the benefit being (potentially a lot, depending on how long DELAYis) more responsive. It also means that the GIL needs to be frequently re-acquired, so that the next sleep can be scheduled, while time.sleepcan release the GIL for the full DELAY. Now, will acquiring the GIL more frequently have a noticeable effect on other threads in your application? Maybe or maybe not. It depends on how many other threads are running and what kind of work loads they have. My guess is it won't be particularly noticeable unless you have a high number of threads, or perhaps another thread doing lots of CPU-bound work, but its easy enough to try it both ways and see.

这实际上意味着使用wait可能比DELAY无条件地完全休眠更需要 CPU ,但好处是(可能很多,取决于多长时间DELAY)响应更快。这也意味着需要频繁地重新获取 GIL,以便可以安排下一次睡眠,同时time.sleep可以为完整的DELAY. 现在,更频繁地获取 GIL 是否会对应用程序中的其他线程产生明显影响?也许或也许不是。这取决于有多少其他线程正在运行以及它们有什么样的工作负载。我的猜测是它不会特别明显,除非您有大量线程,或者可能是另一个线程在执行大量 CPU 密集型工作,但它很容易尝试两种方式并查看。

In Python 3.x, much of the implementation is moved to pure C code:

在 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()

And the C code that acquires the lock:

以及获取锁的 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;
}

This implementation is responsive, and doesn't require frequent wakeups that re-acquire the GIL, so you get the best of both worlds.

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

回答by jeb

Python 2.*
Like @dano said, event.wait is more responsive,
but it can be dangerouswhen the system time is changed backward, while it's waiting!
bug# 1607041: Condition.wait timeout fails on clock change

Python 2.*
就像@dano 所说的,event.wait 响应更快,
但是当系统时间向后更改可能会很危险,而它正在等待!错误#1607041:Condition.wait 超时在时钟更改时失败

See this sample:

请参阅此示例:

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

Normally action()will be called in a 100ms intrvall.
But when you change the time ex. one hour then there is a pause of one hour between two actions.

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

Conclusion: When it's allowed that the time can be change, you should avoid event.wait

结论:当时间可以改变时,你应该避免 event.wait

回答by Science_1

It is interesting to note that the event.wait() method can be invoked on its own:

有趣的是,可以单独调用 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")

So why -not- use wait() as an alternative to sleep() outside of multi-threading? In a word, Zen. (Of course.) Clarity of code is an important thing.

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