Skip to main content

Sincronización

En Python, la biblioteca threading proporciona varias herramientas para sincronización y comunicación entre hilos. Algunas de las más utilizadas son locks, semaphores, conditions, y barriers. Estas herramientas permiten coordinar el acceso a recursos compartidos y sincronizar la ejecución de múltiples hilos para evitar problemas como condiciones de carrera y deadlocks. A continuación, te explico cada una de ellas con ejemplos:

1. Locks:

Un lock es un mecanismo que permite asegurar que solo un hilo a la vez acceda a una sección crítica de código. Se puede usar un lock para evitar que varios hilos accedan simultáneamente a un recurso compartido.

Ejemplo de uso de Lock:

import threading
import time

# Recurso compartido
contador = 0

# Crear un lock
lock = threading.Lock()

def incrementar():
global contador
for _ in range(1000):
lock.acquire() # Adquirir el lock antes de acceder al recurso compartido
contador += 1
lock.release() # Liberar el lock después de modificar el recurso

# Crear dos hilos que ejecuten la función 'incrementar'
hilo1 = threading.Thread(target=incrementar)
hilo2 = threading.Thread(target=incrementar)

# Iniciar los hilos
hilo1.start()
hilo2.start()

# Esperar a que los hilos terminen
hilo1.join()
hilo2.join()

print(f"Contador final: {contador}")

En este ejemplo, dos hilos intentan incrementar un contador compartido. Usamos un lock para asegurar que solo un hilo pueda modificar el contador a la vez, evitando así condiciones de carrera.

2. Semaphores:

Un semaphore es un mecanismo que permite controlar el acceso a un recurso compartido mediante un contador. Los semáforos se pueden utilizar para limitar la cantidad de hilos que pueden acceder a un recurso al mismo tiempo.

Ejemplo de uso de Semaphore:

import threading
import time

# Recurso compartido
recurso = []

# Crear un semáforo con un contador de 2
sem = threading.Semaphore(2)

def usar_recurso():
with sem: # Adquirir el semáforo
print(f"Hilo {threading.current_thread().name} usando el recurso")
time.sleep(2) # Simula el uso del recurso
print(f"Hilo {threading.current_thread().name} liberando el recurso")

# Crear 5 hilos que ejecuten la función 'usar_recurso'
hilos = [threading.Thread(target=usar_recurso, name=f"Hilo-{i+1}") for i in range(5)]

# Iniciar los hilos
for hilo in hilos:
hilo.start()

# Esperar a que los hilos terminen
for hilo in hilos:
hilo.join()

En este ejemplo, creamos un semáforo con un contador de 2, lo que significa que hasta dos hilos pueden acceder al recurso compartido a la vez. Los hilos se bloquean cuando el semáforo llega a 0 y se desbloquean cuando el recurso se libera.

3. Conditions:

Una condition (condición) es un mecanismo que combina un lock con una señalización para coordinar la ejecución de múltiples hilos. Las condiciones permiten a los hilos esperar hasta que una condición específica se cumpla, o notificar a otros hilos cuando se cumple una condición.

Ejemplo de uso de Condition:

import threading
import time

# Crear una condición
condicion = threading.Condition()

# Variable compartida
lista = []

def productor():
with condicion:
for i in range(5):
lista.append(i)
print(f"Productor añadió {i} a la lista")
condicion.notify() # Notifica a los consumidores que se ha añadido un elemento
time.sleep(1) # Simula tiempo de producción

def consumidor():
with condicion:
while True:
condicion.wait() # Espera a que el productor añada un elemento
if lista:
elemento = lista.pop(0)
print(f"Consumidor sacó {elemento} de la lista")
else:
break

# Crear hilos de productor y consumidor
hilo_productor = threading.Thread(target=productor)
hilo_consumidor = threading.Thread(target=consumidor)

# Iniciar los hilos
hilo_productor.start()
time.sleep(1) # Permitir que el productor agregue algunos elementos primero
hilo_consumidor.start()

# Esperar a que los hilos terminen
hilo_productor.join()
hilo_consumidor.join()

En este ejemplo, el hilo productor añade elementos a una lista compartida y notifica a los consumidores. Los consumidores esperan a que el productor añada elementos antes de procesarlos.

4. Barriers:

Una barrier (barrera) es una sincronización que permite que múltiples hilos esperen entre sí antes de continuar con la ejecución. Todos los hilos deben llegar a la barrera para que cualquiera de ellos pueda continuar.

Ejemplo de uso de Barrier:

import threading
import time

# Crear una barrera para 3 hilos
barrera = threading.Barrier(3)

def tarea():
print(f"Hilo {threading.current_thread().name} esperando en la barrera")
barrera.wait() # Espera a que otros hilos lleguen a la barrera
print(f"Hilo {threading.current_thread().name} pasó la barrera")

# Crear 3 hilos que ejecuten la función 'tarea'
hilos = [threading.Thread(target=tarea, name=f"Hilo-{i+1}") for i in range(3)]

# Iniciar los hilos
for hilo in hilos:
hilo.start()

# Esperar a que los hilos terminen
for hilo in hilos:
hilo.join()

En este ejemplo, los 3 hilos se detienen en la barrera y esperan a que todos los demás hilos lleguen antes de continuar con la ejecución. Esto permite sincronizar los hilos en un punto específico de su ejecución.

Estas son las principales herramientas de sincronización y comunicación entre hilos en threading. Son útiles para coordinar el acceso a recursos compartidos y garantizar la correcta ejecución concurrente de hilos en tu programa.