Python time.sleep()与event.wait() [英] Python time.sleep() vs event.wait()

查看:149
本文介绍了Python time.sleep()与event.wait()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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更好地协作?哪个使我的应用程序中的其余线程更具响应性?

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?

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

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

推荐答案

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

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

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

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

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可以释放完整的DELAY的GIL.现在,是否更频繁地获取GIL会对应用程序中的其他线程产生明显的影响?也许不是.这取决于正在运行的其他线程数以及它们承担的工作量.我的猜测是,除非您有大量的线程,或者另一个线程正在执行大量CPU限制的工作,否则它将不会特别引人注目,但是它很容易同时尝试和观察.

This actually means using wait is probably a bit more CPU-hungry than just sleeping the full DELAY unconditionally, but has the benefit being (potentially a lot, depending on how long DELAY is) more responsive. It also means that the GIL needs to be frequently re-acquired, so that the next sleep can be scheduled, while time.sleep can 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.

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

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

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,因此您可以两全其美.

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

这篇关于Python time.sleep()与event.wait()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆