Regla
Asegúrese de que hilo seguro acceso a compartido compartido.
Compartido mutable compartido accesible en a través de múltiples hilos
sin sincronización causa carrera conditions y tiempo de ejecución errores.
Lenguajes compatibles: Python, Java, C#Introducción
Cuando múltiples hilos acceden y modifican variables compartidas sin sincronización, se producen condiciones de carrera. El valor final depende de la temporización impredecible de la ejecución de los hilos, lo que lleva a la corrupción de datos, cálculos incorrectos o errores en tiempo de ejecución. Un contador incrementado por múltiples hilos sin bloqueo perderá actualizaciones a medida que los hilos lean valores obsoletos, los incrementen y escriban resultados conflictivos.
Por qué es importante
Corrupción de datos y resultados incorrectos: Las condiciones de carrera causan una corrupción silenciosa de los datos donde los valores se vuelven inconsistentes o incorrectos. Los saldos de las cuentas pueden ser erróneos, los recuentos de inventario pueden ser negativos o las estadísticas agregadas pueden estar corruptas. Estos errores son difíciles de reproducir porque dependen de la temporización exacta de los hilos.
Inestabilidad del sistema: El acceso no sincronizado a estados compartidos puede provocar el bloqueo de las aplicaciones. Un hilo podría modificar una estructura de datos mientras otro la lee, causando excepciones como errores de puntero nulo o índices fuera de límites. En producción, estos se manifiestan como bloqueos intermitentes bajo carga.
Complejidad de la depuración: Las condiciones de carrera son notoriamente difíciles de depurar porque no son deterministas. El error podría no aparecer en pruebas de un solo hilo o en entornos de baja carga. La reproducción requiere una intercalación de hilos específica que es difícil de forzar, lo que hace que los problemas aparezcan y desaparezcan aleatoriamente.
Ejemplos de código
❌ No conforme:
class BankAccount:
def __init__(self):
self.balance = 0
def deposit(self, amount):
current = self.balance
# Condición de carrera: otro hilo puede modificar el saldo aquí
time.sleep(0.001) # Simula tiempo de procesamiento
self.balance = current + amount
def withdraw(self, amount):
if self.balance >= amount:
current = self.balance
time.sleep(0.001)
self.balance = current - amount
return True
return False
Por qué está mal: Múltiples hilos llamando a deposit() o withdraw() simultáneamente crean condiciones de carrera. Dos hilos depositando $100 cada uno podrían leer el saldo como $0, luego ambos escribir $100, resultando en un saldo final de $100 en lugar de $200.
✅ Conforme:
import threading
class BankAccount:
def __init__(self):
self.__balance = 0
self.__lock = threading.Lock()
@property
def balance(self):
with self.__lock:
return self.__balance
def deposit(self, amount):
with self.__lock:
current = self.__balance
time.sleep(0.001)
self.__balance = current + amount
def withdraw(self, amount):
with self.__lock:
if self.__balance >= amount:
current = self.__balance
time.sleep(0.001)
self.__balance = current - amount
return True
return False
¿Por qué esto importa? El threading.Lock() asegura que solo un hilo acceda al saldo a la vez. Cuando un hilo mantiene el bloqueo, los demás esperan, evitando modificaciones simultáneas. Privado __balance con solo lectura @property evita que el código externo eluda la protección de bloqueo.
Conclusión
Proteja todo el estado mutable compartido con primitivas de sincronización adecuadas como bloqueos, semáforos u operaciones atómicas. Prefiera estructuras de datos inmutables o thread-local storage cuando sea posible. Cuando la sincronización sea necesaria, minimice las secciones críticas para reducir la contención y mejorar el rendimiento.
.avif)
