Dominando la comunicación entre hilos en Python: Sincronización y compartición de datos
La comunicación entre hilos se refiere al proceso de habilitar comunicación y sincronización entre hilos dentro de un programa multi-hilo en Python.
Generalmente, los hilos en Python comparten el mismo espacio de memoria dentro de un proceso, lo que les permite intercambiar datos y coordinar sus actividades a través de variables compartidas, objetos y mecanismos de sincronización especializados proporcionados por el módulo threading.
Para facilitar la comunicación entre hilos, el módulo threading proporciona varias primitivas de sincronización como Locks, Events, Conditions y objetos Semaphores. En este tutorial aprenderá cómo usar los objetos Event y Condition para proporcionar comunicación entre hilos en un programa multi-hilo.
El objeto Event
Un objeto Event gestiona el estado de una bandera interna para que los hilos puedan esperar o establecer. El objeto Event proporciona métodos para controlar el estado de esta bandera, permitiendo a los hilos sincronizar sus actividades basadas en condiciones compartidas.
La bandera es inicialmente falsa y se vuelve verdadera con el método set() y se restablece a falsa con el método clear(). El método wait() bloquea hasta que la bandera sea verdadera.
A continuación se muestran los métodos clave del objeto Event:
- is_set(): Devuelve True si y solo si la bandera interna es verdadera.
- set(): Establece la bandera interna a verdadera. Todos los hilos que esperan a que se vuelva verdadera son despertados. Los hilos que llaman a wait() una vez que la bandera es verdadera no bloquearán en absoluto.
- clear(): Restablece la bandera interna a falsa. Subsecuentemente, los hilos que llaman a wait() bloquearán hasta que set() sea llamado para establecer la bandera interna a verdadera nuevamente.
- wait(timeout=None): Bloquea hasta que la bandera interna sea verdadera. Si la bandera interna es verdadera al entrar, retorna inmediatamente. De lo contrario, bloquea hasta que otro hilo llame a set() para establecer la bandera a verdadera, o hasta que ocurra el timeout opcional. Cuando el argumento timeout está presente y no es None, debería ser un número de punto flotante especificando un timeout para la operación en segundos.
Ejemplo
El siguiente código intenta simular el flujo de tráfico siendo controlado por el estado de la señal de tráfico ya sea VERDE o ROJO.
Hay dos hilos en el programa, apuntando a dos funciones diferentes. La función signal_state() establece y restablece periódicamente el evento indicando cambio de señal de VERDE a ROJO.
La función traffic_flow() espera a que el evento sea establecido, y ejecuta un bucle mientras permanezca establecido.
from threading import Event, Thread import time terminate = False def signal_state(): global terminate while not terminate: time.sleep(0.5) print("Policía de Tráfico Dando Señal VERDE") event.set() time.sleep(1) print("Policía de Tráfico Dando Señal ROJA") event.clear() def traffic_flow(): global terminate num = 0 while num < 10 and not terminate: print("Esperando Señal VERDE") event.wait() print("Señal VERDE ... El tráfico puede moverse") while event.is_set() and not terminate: num += 1 print("Vehículo No:", num," Cruzando la Señal") time.sleep(1) print("Señal ROJA ... El tráfico debe esperar") event = Event() t1 = Thread(target=signal_state) t2 = Thread(target=traffic_flow) t1.start() t2.start() # Terminar los hilos después de algún tiempo time.sleep(5) terminate = True # unir todos los hilos para completar t1.join() t2.join() print("Saliendo del Hilo Principal") Salida
Al ejecutar el código anterior obtendrá la siguiente salida:
Esperando Señal VERDE Policía de Tráfico Dando Señal VERDE Señal VERDE ... El tráfico puede moverse Vehículo No: 1 Cruzando la Señal Policía de Tráfico Dando Señal ROJA Señal ROJA ... El tráfico debe esperar Esperando Señal VERDE Policía de Tráfico Dando Señal VERDE Señal VERDE ... El tráfico puede moverse Vehículo No: 2 Cruzando la Señal Vehículo No: 3 Cruzando la Señal Policía de Tráfico Dando Señal ROJA Policía de Tráfico Dando Señal VERDE Vehículo No: 4 Cruzando la Señal Policía de Tráfico Dando Señal ROJA Señal ROJA ... El tráfico debe esperar Policía de Tráfico Dando Señal VERDE Policía de Tráfico Dando Señal ROJA Saliendo del Hilo Principal
El objeto Condition
El objeto Condition en el módulo threading de Python proporciona un mecanismo de sincronización más avanzado. Permite a los hilos esperar una notificación de otro hilo antes de proceder. Los objetos Condition siempre están asociados con un candado y proporcionan mecanismos para señalización entre hilos.
A continuación se muestra la sintaxis de la clase threading.Condition():
threading.Condition(lock=None)
A continuación se muestran los métodos clave del objeto Condition:
- acquire(*args): Adquiere el candado subyacente. Este método llama al método correspondiente en el candado subyacente; el valor de retorno es lo que ese método retorna.
- release(): Libera el candado subyacente. Este método llama al método correspondiente en el candado subyacente; no hay valor de retorno.
- wait(timeout=None): Este método libera el candado subyacente, y luego bloquea hasta que es despertado por una llamada a notify() o notify_all() para la misma variable de condición en otro hilo, o hasta que ocurra el timeout opcional. Una vez despertado o agotado el tiempo, re-adquiere el candado y retorna.
- wait_for(predicate, timeout=None): Este método de utilidad puede llamar a wait() repetidamente hasta que el predicado se satisfaga, o hasta que ocurra un timeout. El valor de retorno es el último valor de retorno del predicado y evaluará a False si el método agotó el tiempo.
- notify(n=1): Este método despierta como máximo n de los hilos que esperan la variable de condición; es una no-op si no hay hilos esperando.
- notify_all(): Despierta a todos los hilos que esperan en esta condición. Este método actúa como notify(), pero despierta a todos los hilos en espera en lugar de uno. Si el hilo que llama no ha adquirido el candado cuando se llama a este método, se lanza un RuntimeError.
Ejemplo
Este ejemplo demuestra una forma simple de comunicación entre hilos usando el objeto Condition del módulo threading de Python. Aquí thread_a y thread_b se comunican usando un objeto Condition, thread_a espera hasta que recibe una notificación de thread_b. thread_b duerme por 2 segundos antes de notificar a thread_a y luego termina.
from threading import Condition, Thread import time c = Condition() def thread_a(): print("Hilo A iniciado") with c: print("Hilo A esperando permiso...") c.wait() print("¡Hilo A obtuvo permiso!") print("Hilo A finalizado") def thread_b(): print("Hilo B iniciado") with c: time.sleep(2) print("Notificando a Hilo A...") c.notify() print("Hilo B finalizado") Thread(target=thread_a).start() Thread(target=thread_b).start() Salida
Al ejecutar el código anterior obtendrá la siguiente salida:
Hilo A iniciado Hilo A esperando permiso... Hilo B iniciado Notificando a Hilo A... Hilo B finalizado ¡Hilo A obtuvo permiso! Hilo A finalizado
Ejemplo
Aquí hay otro código que demuestra cómo se usa el objeto Condition para proporcionar comunicación entre hilos. En este, el hilo t2 ejecuta la función taskB(), y el hilo t1 ejecuta la función taskA(). El hilo t1 adquiere la condición y la notifica.
Para ese momento, el hilo t2 está en estado de espera. Después de que la condición se libera, el hilo en espera procede a consumir el número aleatorio generado por la función notificadora.
from threading import Condition, Thread import time import random numbers = [] def taskA(c): for _ in range(5): with c: num = random.randint(1, 10) print("Número aleatorio generado:", num) numbers.append(num) print("Notificación emitida") c.notify() time.sleep(0.3) def taskB(c): for i in range(5): with c: print("esperando actualización") while not numbers: c.wait() print("Obtenido número aleatorio", numbers.pop()) time.sleep(0.3) c = Condition() t1 = Thread(target=taskB, args=(c,)) t2 = Thread(target=taskA, args=(c,)) t1.start() t2.start() t1.join() t2.join() print("Listo") Cuando ejecuta este código, producirá la siguiente salida:
esperando actualización Número aleatorio generado: 2 Notificación emitida Obtenido número aleatorio 2 Número aleatorio generado: 5 Notificación emitida esperando actualización Obtenido número aleatorio 5 Número aleatorio generado: 1 Notificación emitida esperando actualización Obtenido número aleatorio 1 Número aleatorio generado: 9 Notificación emitida esperando actualización Obtenido número aleatorio 9 Número aleatorio generado: 2 Notificación emitida esperando actualización Obtenido número aleatorio 2 Listo
python
- Cómo Eliminar Elementos Duplicados de una Lista en Python: 8 Métodos Efectivos
- Sentencias, indentación y comentarios en Python: Guía clara
- Dominando Python: 6 formas efectivas de calcular el cuadrado de un número
- Crea un juego Tres en Raya en Python: Tutorial paso a paso con código
- Domine el Formateo de Strings en Python con format(): Ejemplos Claros
- Dominando sentencias if…else en Python
- Dominando la palabra clave global en Python: Cómo y cuándo usarla
- Dominando el Manejo de Excepciones en Python: try, except, else y finally Explicados
- Dominando la Programación Orientada a Objetos en Python: Clases, Herencia, Encapsulación y Polimorfismo
- Domine str.count() en Python: Cómo Contar Caracteres y Subcadenas con Ejemplos
- Entrada/Salida de Archivos en Python: Domina las Operaciones I/O