thread python multithreading sleep

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