exit_flag.wait(timeout=DELAY)
Verwendung wird mehr ansprechbar, weil Sie sofort aus der while-Schleife brechen werden, wenn exit_flag
ist eingestellt. Mit time.sleep
, selbst nachdem das Ereignis eingestellt ist, werden Sie im time.sleep
Anruf warten, bis Sie für DELAY
Sekunden geschlafen haben.
Im Hinblick auf die Implementierung haben Python 2.x und Python 3.x ein sehr unterschiedliches Verhalten. In Python 2.x Event.wait
in reinem Python mit einer Reihe von kleinen time.sleep
Anrufe implementiert:
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)
Das bedeutet eigentlich wait
verwendet, ist wahrscheinlich ein bisschen mehr CPU-hungrig ist als nur die volle DELAY
bedingungslos schlafen, hat aber die profitieren (möglicherweise viel, je nachdem wie lange DELAY
ist) reaktionsfähiger. Es bedeutet auch, dass die GIL häufig neu erworben werden muss, damit der nächste Schlaf geplant werden kann, während time.sleep
die GIL für die volle DELAY
freigeben kann. Wird sich der Erwerb der GIL häufiger auf andere Threads in Ihrer Anwendung auswirken? Vielleicht, vielleicht auch nicht. Es hängt davon ab, wie viele andere Threads ausgeführt werden und welche Art von Arbeitslast sie haben. Meine Vermutung ist, dass es nicht besonders auffällt, es sei denn, Sie haben eine hohe Anzahl von Threads oder vielleicht einen anderen Thread, der viel CPU-gebundene Arbeit erledigt, aber es ist einfach genug, um es in beide Richtungen zu probieren und zu sehen.
In Python 3.x wird viel von der Umsetzung der reinen C-Code bewegt:
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()
und die C-Code, der die Sperre erwirbt:
/* 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;
}
Diese Implementierung reagiert, und erfordert keine häufigen Wakeups, die das GIL wiedererlangen, so dass Sie das Beste aus beiden Welten erhalten.
vermeiden Wo ist der Code, der die 'exit' Flag setzt? Ist es in der 'action()' -Aufruf oder in einem anderen Thread oder vielleicht von einem Signalhandler aufgerufen? – tdelaney
Ich benutze 'Event.wait' in dieser Situation, obwohl Python 2.x im Hintergrund abfragt. In Intervallen von etwa 1 Sekunde zu schlafen ist einigermaßen reaktionsfähig und weniger aufdringlich. – tdelaney
Der erste wird einige CPU-Zeit verschwenden, für eine Sache. – immibis