Python time.sleep() 对比 event.wait()
Python time.sleep() vs event.wait()
我想在我的多线程 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 配合使用?哪一个使我的应用程序中的其余线程响应更快?
(假设一些外部事件集exit
或exit_flag
,我愿意在关闭时等待完整的延迟)
使用 exit_flag.wait(timeout=DELAY)
会更灵敏,因为设置 exit_flag
后您会立即跳出 while 循环。使用 time.sleep
,即使在事件设置之后,您仍将在 time.sleep
调用中等待,直到您睡了 DELAY
秒。
在实现方面,Python 2.x 和 Python 3.x 有非常不同的行为。在 Python 2.x Event.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代码:
/* 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,因此您可以两全其美。
Python2.*
正如@dano 所说,event.wait 响应更快,
但是当系统时间向后更改时,它可能很危险,而它正在等待!
bug# 1607041: Condition.wait timeout fails on clock change
查看此示例:
def someHandler():
while not exit_flag.wait(timeout=0.100):
action()
通常action()
会在100ms的间隔内被调用。
但是当你改变时间前。一小时 然后两个动作之间有一个小时的停顿。
结论:当允许更改时间时,你应该避免event.wait
,它可能是灾难性的!
Python 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() 的替代方法呢?一言以蔽之,禅。 (当然。)代码的清晰度很重要。
根据我的经验,使用 time.sleep() 会吃掉 CPU 并使应用程序延迟,这是因为睡眠功能是其他线程的阻塞方法,而 Event.wait() 是其他线程的非阻塞方法。
您可以通过查看线程等待锁释放所需的时间来了解这一点!
此外,如果您不知道阻塞线程所需的时间,Event.wait() 将非常有用!这样您就可以 set
和 clear
事件
我想在我的多线程 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 配合使用?哪一个使我的应用程序中的其余线程响应更快?
(假设一些外部事件集exit
或exit_flag
,我愿意在关闭时等待完整的延迟)
使用 exit_flag.wait(timeout=DELAY)
会更灵敏,因为设置 exit_flag
后您会立即跳出 while 循环。使用 time.sleep
,即使在事件设置之后,您仍将在 time.sleep
调用中等待,直到您睡了 DELAY
秒。
在实现方面,Python 2.x 和 Python 3.x 有非常不同的行为。在 Python 2.x Event.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代码:
/* 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,因此您可以两全其美。
Python2.*
正如@dano 所说,event.wait 响应更快,
但是当系统时间向后更改时,它可能很危险,而它正在等待!
bug# 1607041: Condition.wait timeout fails on clock change
查看此示例:
def someHandler():
while not exit_flag.wait(timeout=0.100):
action()
通常action()
会在100ms的间隔内被调用。
但是当你改变时间前。一小时 然后两个动作之间有一个小时的停顿。
结论:当允许更改时间时,你应该避免event.wait
,它可能是灾难性的!
Python 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() 的替代方法呢?一言以蔽之,禅。 (当然。)代码的清晰度很重要。
根据我的经验,使用 time.sleep() 会吃掉 CPU 并使应用程序延迟,这是因为睡眠功能是其他线程的阻塞方法,而 Event.wait() 是其他线程的非阻塞方法。
您可以通过查看线程等待锁释放所需的时间来了解这一点!
此外,如果您不知道阻塞线程所需的时间,Event.wait() 将非常有用!这样您就可以 set
和 clear
事件