La concurrencia ofrece mejoras de respuesta y paralelismo, pero también introduce errores difíciles de depurar. Estas son las trampas más habituales y cómo esquivarlas.
La clave es medir, limitar la cantidad de workers y aplicar patrones seguros de sincronización.
asyncio si necesitas miles de sockets.cpu_count() o ligeramente menos.shared_memoryEl siguiente código simula un deadlock al adquirir locks en orden inverso, registra el orden de adquisición y luego muestra una versión corregida con orden consistente y timeout.
import logging
import threading
import time
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(threadName)s] %(message)s",
datefmt="%H:%M:%S",
)
lock_a = threading.Lock()
lock_b = threading.Lock()
def tarea_mala():
logging.debug("Intentando lock A")
with lock_a:
logging.debug("Tomó lock A, dormir")
time.sleep(0.5)
logging.debug("Intentando lock B")
with lock_b:
logging.debug("Nunca debería llegar aquí (deadlock)")
def tarea_mala_inversa():
logging.debug("Intentando lock B")
with lock_b:
logging.debug("Tomó lock B, dormir")
time.sleep(0.5)
logging.debug("Intentando lock A")
with lock_a:
logging.debug("Nunca debería llegar aquí (deadlock)")
def tarea_buena():
# Orden consistente: siempre A luego B
acquired_a = lock_a.acquire(timeout=1)
if not acquired_a:
logging.debug("No se pudo tomar lock A, abortando")
return
try:
logging.debug("Tomó lock A, dormir")
time.sleep(0.2)
acquired_b = lock_b.acquire(timeout=1)
if not acquired_b:
logging.debug("Timeout en lock B, liberando A")
return
try:
logging.debug("Trabajo hecho sin deadlock")
finally:
lock_b.release()
finally:
lock_a.release()
if __name__ == "__main__":
h1 = threading.Thread(target=tarea_mala, name="mala-A-primero")
h2 = threading.Thread(target=tarea_mala_inversa, name="mala-B-primero")
h1.start()
h2.start()
time.sleep(1.5) # deja que se bloquee
logging.debug("Los hilos quedaron bloqueados (deadlock)")
# Versión corregida
lock_a = threading.Lock()
lock_b = threading.Lock()
buenos = [threading.Thread(target=tarea_buena, name=f"bueno-{i}") for i in range(2)]
for h in buenos:
h.start()
for h in buenos:
h.join()
logging.debug("Terminó versión corregida sin deadlocks")
El logging muestra cómo dos hilos se bloquean mutuamente al tomar locks en distinto orden. La versión corregida impone un orden fijo y timeouts para salir sin quedar atrapados, exponiendo el estado en los logs para depurar.
Aplica el mismo principio en código real: orden consistente de locks, timeouts y un canal de logging con timestamps.