Problema: saber cuándo comenzará a ejecutarse realmente un subproceso iniciado.
Una característica clave de los subprocesos es que se ejecutan de forma independiente y no determinista. Esto puede presentar un problema complicado de sincronización si otros subprocesos en el programa necesitan saber si un subproceso ha llegado a cierto punto en su ejecución antes de realizar más operaciones. Para resolver estos problemas, utilice el objeto Event de la biblioteca de subprocesos .
Las instancias de eventos son similares a un indicador «pegajoso» que permite que los subprocesos esperen a que suceda algo. Inicialmente, un evento se establece en 0. Si el evento no se establece y un subproceso espera el evento, se bloqueará (es decir, se dormirá) hasta que se establezca el evento. Un subproceso que establece el evento activará todos los subprocesos que estén esperando (si los hay). Si un subproceso espera un evento que ya se ha establecido, simplemente continúa y continúa ejecutándose.
Código #1: Código que usa un Evento para coordinar el inicio de un hilo.
from threading import Thread, Event import time # Code to execute in an independent thread def countdown(n, started_evt): print('countdown starting') started_evt.set() while n > 0: print('T-minus', n) n -= 1 time.sleep(5) # Create the event object that # will be used to signal startup started_evt = Event() # Launch the thread and pass the startup event print('Launching countdown') t = Thread(target = countdown, args =(10, started_evt)) t.start() # Wait for the thread to start started_evt.wait() print('countdown is running')
Al ejecutar el código anterior, el mensaje «se está ejecutando la cuenta regresiva» siempre aparecerá después del mensaje «comenzando la cuenta regresiva». Esto está coordinado por el evento que hace que el subproceso principal espere hasta que la countdown()
función haya impreso por primera vez el mensaje de inicio.
- Los objetos de evento se utilizan mejor para eventos de una sola vez. Es decir, crea un evento, los subprocesos esperan a que se establezca el evento y, una vez establecido, el evento se descarta.
- Aunque es posible borrar un evento utilizando este
clear()
método, borrar un evento de forma segura y esperar a que se vuelva a configurar es difícil de coordinar y puede provocar la pérdida de eventos, bloqueos u otros problemas (en particular, no se puede garantizado que una solicitud para borrar un evento después de configurarlo se ejecutará antes de que un subproceso liberado vuelva a esperar el evento nuevamente). - Si un subproceso va a señalar repetidamente un evento una y otra vez, probablemente sea mejor usar un objeto Condition en su lugar.
Código #2: Implementar un temporizador periódico que otros subprocesos pueden monitorear para ver cuándo expira el temporizador.
import threading import time class PeriodicTimer: def __init__(self, interval): self._interval = interval self._flag = 0 self._cv = threading.Condition() def start(self): t = threading.Thread(target = self.run) t.daemon = True t.start() def run(self): ''' Run the timer and notify waiting threads after each interval ''' while True: time.sleep(self._interval) with self._cv: self._flag ^= 1 self._cv.notify_all() def wait_for_tick(self): ''' Wait for the next tick of the timer ''' with self._cv: last_flag = self._flag while last_flag == self._flag: self._cv.wait()
Código #3: Uso del temporizador
# Example use of the timer ptimer = PeriodicTimer(5) ptimer.start() # Two threads that synchronize on the timer def countdown(nticks): while nticks > 0: ptimer.wait_for_tick() print('T-minus', nticks) nticks -= 1 def countup(last): n = 0 while n < last: ptimer.wait_for_tick() print('Counting', n) n += 1 threading.Thread(target = countdown, args =(10, )).start() threading.Thread(target = countup, args =(5, )).start()
Una característica fundamental de los objetos Event es que activan todos los subprocesos en espera. Si se escribe un programa en el que solo se desea activar un único subproceso en espera, probablemente sea mejor usar un objeto Semáforo o Condición en su lugar.
Código #4: Código que involucra semáforos
def worker(n, sema): # Wait to be signaled sema.acquire() # Do some work print('Working', n) # Create some threads sema = threading.Semaphore(0) nworkers = 10 for n in range(nworkers): t = threading.Thread(target = worker, args =(n, sema, )) t.start()
Al ejecutar este código, se iniciará un conjunto de subprocesos, pero no sucede nada porque todos están bloqueados esperando adquirir el semáforo. Cada vez que se suelta el semáforo, solo un trabajador se despertará y se ejecutará como se muestra en el código debajo del
Código #5:
sema.release() sema.release()
Producción :
Working 0 Working 1
Escribir código que implique mucha sincronización complicada entre subprocesos probablemente hará que tu cabeza explote. Un enfoque más sensato es enhebrar subprocesos como tareas de comunicación mediante colas o como actores.
Publicación traducida automáticamente
Artículo escrito por manikachandna97 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA