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.