thread - time.sleep python
Python time.sleep() vs event.wait() (3)
Quiero realizar una acción a intervalos regulares en mi aplicación Python multiproceso. He visto dos formas diferentes de hacerlo
exit = False
def thread_func():
while not exit:
action()
time.sleep(DELAY)
o
exit_flag = threading.Event()
def thread_func():
while not exit_flag.wait(timeout=DELAY):
action()
¿Hay alguna ventaja en un sentido sobre el otro? ¿Se usan menos recursos o se juegan mejor con otros hilos y el GIL? ¿Cuál hace que los hilos restantes de mi aplicación respondan mejor?
(Supongamos que algunos conjuntos de eventos externos
exit
o
exit_flag
, y estoy dispuesto a esperar el retraso completo mientras se apaga)
El uso de
exit_flag.wait(timeout=DELAY)
será más receptivo, porque saldrá del bucle while instantáneamente cuando se establezca
exit_flag
.
Con
time.sleep
, incluso después de que se haya configurado el evento, esperará durante la llamada
time.sleep
hasta que haya dormido durante
DELAY
segundos.
En términos de implementación, Python 2.xy Python 3.x tienen un comportamiento muy diferente.
En Python 2.x,
Event.wait
se implementa en Python puro usando un montón de pequeñas llamadas
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)
En realidad, esto significa que el uso de
wait
probablemente requiera un poco más de CPU que simplemente dormir sin
DELAY
el
DELAY
completo, pero tiene el beneficio de ser (potencialmente mucho, dependiendo de cuánto tiempo
DELAY
) sea más receptivo.
También significa que el GIL debe volver a adquirirse con frecuencia, de modo que se pueda programar el próximo sueño, mientras que
time.sleep
puede liberar el GIL para el
DELAY
completo.
Ahora, ¿adquirir el GIL con mayor frecuencia tendrá un efecto notable en otros hilos en su aplicación?
Tal vez o tal vez no.
Depende de cuántos otros subprocesos se estén ejecutando y qué tipo de cargas de trabajo tengan.
Supongo que no será particularmente notable a menos que tenga una gran cantidad de subprocesos, o tal vez otro subproceso que haga mucho trabajo vinculado a la CPU, pero es lo suficientemente fácil como para intentarlo en ambos sentidos y ver.
En Python 3.x, gran parte de la implementación se mueve a código C puro:
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()
Y el código C que adquiere el bloqueo:
/* 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;
}
Esta implementación es receptiva y no requiere activaciones frecuentes que vuelvan a adquirir el GIL, por lo que obtendrá lo mejor de ambos mundos.
Es interesante observar que el método event.wait () se puede invocar por sí solo:
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")
Entonces, ¿por qué no usar wait () como alternativa a sleep () fuera de multi-threading? En una palabra, zen. (Por supuesto). La claridad del código es algo importante.
Python 2. *
Como dijo @dano, event.wait responde mejor,
pero
puede ser peligroso
cuando la hora del sistema
se cambia hacia atrás
, ¡mientras está esperando!
error # 1607041: el tiempo de espera de Condition.wait falla al cambiar el reloj
Ver esta muestra:
def someHandler():
while not exit_flag.wait(timeout=0.100):
action()
Normalmente se llamará a
action()
en una intrvall de 100 ms.
Pero cuando cambias la hora ex.
una hora, luego hay una pausa de una hora entre dos acciones.
Conclusión: cuando se permite que se pueda cambiar el tiempo, debe evitar
event.wait