Exclusión, sincronización y región
La programación concurrente, esencial en entornos de desarrollo modernos, involucra la ejecución simultánea de múltiples hilos o procesos para mejorar la eficiencia y la capacidad de respuesta de un programa.
Conceptos clave como la exclusión mutua, las regiones críticas y la sincronización juegan un papel fundamental. La exclusión mutua asegura que solo un hilo acceda a un recurso compartido en un momento dado, evitando así condiciones de carrera y garantizando la coherencia de los datos. Las regiones críticas, por su parte, son secciones de código donde se accede y modifica información compartida, y se implementan para garantizar que estas operaciones críticas se realicen de manera atómica, sin interferencia de otros hilos. La sincronización, coordina la ejecución de hilos para evitar conflictos y lograr un comportamiento predecible, utilizando mecanismos como cerrojos, variables de condición y semáforos. Estos conceptos son esenciales para desarrollar aplicaciones robustas y eficientes en entornos concurrentes. A continuación se muestra unos ejemplos
Ejemplo de exclusión mutua
Supongamos que se tiene una variable global compartida llamada contador
. Se quiere garantizar que varios hilos puedan incrementar este contador sin que se pierdan actualizaciones debido a condiciones de carrera.
import threading contador = 0 mutex = threading.Lock() def incrementar(): global contador for _ in range(1000000): with mutex: contador += 1 # Crear hilos hilo_1 = threading.Thread(target=incrementar) hilo_2 = threading.Thread(target=incrementar) # Iniciar hilos hilo_1.start() hilo_2.start() # Esperar a que ambos hilos terminen hilo_1.join() hilo_2.join() print("Valor final del contador:", contador)
En este ejemplo, la exclusión mutua se logra utilizando un cerrojo (mutex
). La sección crítica es la operación de incremento dentro del bucle. Solo un hilo puede ejecutar esta sección a la vez, asegurando que las actualizaciones al contador sean coherentes.
Ejemplo de de Región Crítica
Supongamos que se tiene una lista compartida y varios hilos que quieren agregar elementos a esta lista. Se necesita asegurar que la operación de agregar elementos se realice de manera segura.
import threading lista_compartida = [] mutex = threading.Lock() def agregar_elemento(elemento): with mutex: lista_compartida.append(elemento) # Crear hilos hilo_1 = threading.Thread(target=agregar_elemento, args=("A",)) hilo_2 = threading.Thread(target=agregar_elemento, args=("B",)) # Iniciar hilos hilo_1.start() hilo_2.start() # Esperar a que ambos hilos terminen hilo_1.join() hilo_2.join() print("Lista final:", lista_compartida)
Aquí, la región crítica es la operación de agregar elementos a la lista. El cerrojo (mutex
) garantiza que solo un hilo pueda realizar esta operación a la vez, evitando problemas de concurrencia.
Ejemplo de Sincronización
Suponga que se tiene dos hilos: uno para imprimir números pares y otro para imprimir números impares en orden. Se Necesita sincronizarlos para asegurar que impriman alternadamente en orden.
import threading mutex = threading.Lock() condicion = threading.Condition() def imprimir_pares(): with mutex: for i in range(0, 10, 2): print(i) condicion.notify() condicion.wait() def imprimir_impares(): with mutex: for i in range(1, 10, 2): print(i) condicion.notify() condicion.wait() # Crear hilos hilo_pares = threading.Thread(target=imprimir_pares) hilo_impares = threading.Thread(target=imprimir_impares) # Iniciar hilos hilo_pares.start() hilo_impares.start() # Esperar a que ambos hilos terminen hilo_pares.join() hilo_impares.join()
Aquí, se utiliza un objeto de condición (condicion
) para sincronizar los hilos. La función notify()
señala al otro hilo que puede imprimir, mientras que wait()
hace que un hilo espere hasta que sea notificado por el otro. Esto asegura que los números pares e impares se impriman alternadamente en orden.