Règle
Assurer l'accès l'accès aux partagé partagé.
Les états partagés mutable mutable accessible par plusieurs threads
sans synchronisation provoque course conditions et d'exécution d'exécution.
Langages pris en charge : Python, Java, C#Introduction
Lorsque plusieurs threads accèdent à des variables partagées et les modifient sans synchronisation, des conditions de course se produisent. La valeur finale dépend de la synchronisation imprévisible de l'exécution des threads, ce qui entraîne une corruption des données, des calculs incorrects ou des erreurs d'exécution. Un compteur incrémenté par plusieurs threads sans verrouillage manquera des mises à jour lorsque les threads liront des valeurs périmées, les incrémenteront et écriront des résultats contradictoires.
Pourquoi c'est important
Corruption des données et résultats incorrects : Les conditions de course provoquent une corruption silencieuse des données où les valeurs deviennent incohérentes ou incorrectes. Les soldes des comptes peuvent être erronés, les inventaires peuvent être négatifs ou les statistiques agrégées peuvent être corrompues. Ces bogues sont difficiles à reproduire car ils dépendent de la synchronisation exacte des threads.
Instabilité du système : L'accès non synchronisé à l'état partagé peut faire planter les applications. Un thread peut modifier une structure de données pendant qu'un autre la lit, ce qui provoque des exceptions telles que des erreurs de pointeur nul ou des index hors limites. En production, ces exceptions se manifestent par des pannes intermittentes sous charge.
Complexité du débogage : Les conditions de course sont notoirement difficiles à déboguer parce qu'elles ne sont pas déterministes. Le bogue peut ne pas apparaître dans les tests à un seul thread ou dans les environnements à faible charge. La reproduction nécessite un entrelacement spécifique des threads qui est difficile à forcer, ce qui fait que les problèmes apparaissent et disparaissent de manière aléatoire.
Exemples de code
❌ Non conforme :
class BankAccount:
def __init__(self) :
self.balance = 0
def deposit(self, amount) :
current = self.balance
# Condition de course : un autre thread peut modifier le solde ici
time.sleep(0.001) # Simule le temps de traitement
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
Pourquoi ce n'est pas correct : Plusieurs threads appelant simultanément deposit() ou withdraw() créent des conditions de course. Deux threads déposant chacun 100 $ peuvent lire le solde à 0 $, puis écrire tous les deux 100 $, ce qui donne un solde final de 100 $ au lieu de 200 $.
✅ Conforme :
importer le filage
classe Compte bancaire:
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 :
si self.__balance >= amount :
current = self.__balance
time.sleep(0.001)
self.__balance = current - amount
return True
return False
Pourquoi cela est-il important ? Le threading.Lock() garantit qu'un seul thread accède à l'équilibre à la fois. Lorsqu'un thread détient le verrou, les autres attendent, ce qui empêche les modifications simultanées. Privé Équilibre avec lecture seule @propriété empêche le code externe de contourner la protection de la serrure.
Conclusion
Protéger tous les états mutables partagés à l'aide de primitives de synchronisation appropriées telles que les verrous, les sémaphores ou les opérations atomiques. Préférez les structures de données immuables ou le stockage local des threads lorsque cela est possible. Lorsque la synchronisation est nécessaire, minimisez les sections critiques afin de réduire les conflits et d'améliorer les performances.
.avif)
