Regra
Garantir thread-safe acesso a partilhado partilhado.
Partilhado mutável estado acedido por múltiplas threads
sem sincronização causa corrida condições e tempo de execução erros.
Linguagens suportadas: Python, Java, C#Introdução
Quando várias threads acedem e modificam variáveis partilhadas sem sincronização, ocorrem condições de corrida. O valor final depende do tempo de execução imprevisível da thread, levando à corrupção de dados, cálculos incorrectos ou erros de tempo de execução. Um contador incrementado por várias threads sem bloqueio perderá actualizações à medida que as threads lêem valores obsoletos, os incrementam e escrevem resultados contraditórios.
Porque é importante
Corrupção de dados e resultados incorrectos: As condições de corrida causam corrupção silenciosa de dados, em que os valores se tornam inconsistentes ou incorrectos. Os saldos das contas podem estar errados, as contagens de inventário podem ser negativas ou as estatísticas agregadas podem ser corrompidas. Estes erros são difíceis de reproduzir porque dependem do tempo exato do thread.
Instabilidade do sistema: O acesso não sincronizado ao estado partilhado pode bloquear as aplicações. Uma thread pode modificar uma estrutura de dados enquanto outra a lê, causando excepções como erros de ponteiro nulo ou índice fora dos limites. Na produção, isso se manifesta como falhas intermitentes sob carga.
Complexidade de depuração: As condições de corrida são notoriamente difíceis de depurar porque são não-determinísticas. O bug pode não aparecer em testes de thread único ou em ambientes de baixa carga. A reprodução requer uma intercalação específica de threads que é difícil de forçar, fazendo com que os problemas apareçam e desapareçam aleatoriamente.
Exemplos de código
Não conforme:
class BankAccount:
def __init__(self):
self.balance = 0
def deposit(self, amount):
current = self.balance
# Condição de corrida: outra thread pode modificar o saldo aqui
time.sleep(0.001) # Simula o tempo de processamento
self.balance = current + amount
def withdraw(self, amount):
if self.balance >= amount:
current = self.balance
time.sleep(0.001)
saldo próprio = atual - montante
return True
return False
Porque é que está errado: Várias threads que chamam deposit() ou withdraw() simultaneamente criam condições de corrida. Duas threads que depositam $100 cada uma podem ler o saldo como $0 e depois escrever $100, resultando num saldo final de $100 em vez de $200.
Conformidade:
importar 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
Porque é que isto é importante: O threading.Lock() assegura que apenas um thread acede ao saldo de cada vez. Quando um thread mantém o bloqueio, os outros esperam, impedindo modificações simultâneas. Privado __equilíbrio com carácter só de leitura @propriedade impede que um código externo contorne a proteção do cadeado.
Conclusão
Proteger todo o estado mutável partilhado com primitivas de sincronização adequadas, como bloqueios, semáforos ou operações atómicas. Prefira estruturas de dados imutáveis ou armazenamento local de thread quando possível. Quando a sincronização for necessária, minimize as secções críticas para reduzir a contenção e melhorar o desempenho.
.avif)
