Programación Concurrente en Python: Bloqueos, Sincronización y Semáforos

Conceptos Fundamentales

①Concurrencia: Capacidad de un sistema para procesar múltiples tareas durante un período de tiempo ②Paralelismo: Capacidad de un sistema para prcoesar múltiples tareas simultáneamente ③Síncrono: Cuando un proceso encuentra una operación de E/S (esperando datos externos), se detiene y no continúa su ejecución. ④Asíncrono: Cuando un proceso encuentra una operación de E/S, no se detiene, sino que continúa ejecutando otro código hasta recibir los datos, luego procesa. ⑤GIL: Global Interpreter Lock, es un bloqueo implementado por el intérprete CPython. Sin importar cuántos CPUs tenga el sistema, en un mismo proceso, aunque existan múltiples hilos, solo uno puede ejecutarse a la vez. ⑥Tipos de tareas: Densas en E/S y densas en cómputo. Para tareas densas en E/S, los hilos múltiples de Python pueden acelerar la ejecución, también se puede usar multiprocesamiento + corrutinas. Para tareas densas en cómputo, los hilos múltiples de Python pueden aumentar el tiempo de procesamiento debido a la sobrecarga de cambio de contexto.

Bloqueos de Sincronización

Los bloqueos de sincronización también se conocen como mutex o bloqueos mutuamente excluyentes. (1) Sin bloqueo de sincronización

import threading
import time

def decrementar():
    global contador
    temp = contador
    time.sleep(0.001)
    contador = temp - 1

contador = 100

lista_hilos = []

for i in range(100):
    t = threading.Thread(target=decrementar)
    t.start()
    lista_hilos.append(t)

for hilo in lista_hilos:
    hilo.join()

print(contador)  # Resultado impredecible, probablemente mayor que 0


En el código anterior, cuando los hilos son interrumpidos por el planificador del sistema, es posible que un hilo aún no haya completado su ejecución antes de que el valor de contador sea modificado, causando resultados inconsistentes (dependiendo de la velocidad de ejecución de la CPU) (2) Con bloqueo de sincronización

import threading
import time

def decrementar():
    global contador
    bloqueo.acquire()  # Solicitar bloqueo
    temp = contador
    time.sleep(0.001)
    contador = temp - 1
    bloqueo.release()  # Liberar bloqueo

contador = 100
lista_hilos = []
bloqueo = threading.Lock()  # Crear objeto de bloqueo

for i in range(100):
    t = threading.Thread(target=decrementar)
    t.start()
    lista_hilos.append(t)

for hilo in lista_hilos:
    hilo.join()

print(contador)  # 0


Después de ejecutar bloqueo.acquire(), el hilo actual tiene acceso exclusivo al recurso compartido y no puede ser interrumpido para ejecutar el código dentro de la función. Otros hilos se bloquean en la llamada a bloqueo.acquire(). Solo después de ejecutar bloqueo.release(), otros hilos pueden adquirir el bloqueo y continuar su ejecución.

Bloqueos Muertos y Bloqueos Recursivos

Cuando varios hilos comparten múltiples recursos, si dos hilos retienen recursos respectivos y esperan los recursos del otro, ocurrirá un bloqueo muerto. Estos dos hilos esperarán indefinidamente sin entervención externa.

import threading
import time

class MiHilo(threading.Thread):
    def ejecutarA(self):
        recursoA.acquire()
        print(self.nombre, "adquirió recursoA", time.strftime("%X"))
        time.sleep(1)
        
        recursoB.acquire()
        print(self.nombre, "adquirió recursoB", time.strftime("%X"))
        time.sleep(1)
        
        recursoB.release()
        recursoA.release()
        
    def ejecutarB(self):
        recursoB.acquire()
        print(self.nombre, "adquirió recursoB", time.strftime("%X"))
        time.sleep(1)
        
        recursoA.acquire()
        print(self.nombre, "adquirió recursoA", time.strftime("%X"))
        time.sleep(1)
        
        recursoA.release()
        recursoB.release()

recursoA = threading.Lock()
recursoB = threading.Lock()

hilo1 = MiHilo(name="Hilo-1")
hilo2 = MiHilo(name="Hilo-2")

hilo1.start()
hilo2.join()

print("Ejecución completada")

Este ejemplo ilustra una situación de bloqueo muerto donde dos hilos intentan adquirir dos recursos en orden diferente, lo que provoca que cada hilo espere indefinidamente al recurso retenido por el otro hilo.

Etiquetas: Python concurrencia hilos sincronización Bloqueos

Publicado el 7-11 01:35