Como um Intertangle Forma
Dois subsistemas começam a vida como módulos independentes. Com o tempo, cada um acumula um campo em um objeto-deus compartilhado: uma estrutura de configuração global, um gerenciador singleton, uma classe estática. Cada adição: correta em isolamento. O acoplamento: invisível em testes de pequena escala.
Três substratos onde isso calcificou:
VLC player de mídia. Áudio, vídeo e playlist compartilham um único lock guardando um estado de player global. Uma solicitação de pulo para um ponto de tempo adquire o lock, modifica a posição de reprodução e esvazia o buffer de áudio. O subsistema de vídeo, esperando pelo mesmo lock, fica parado. O subsistema de playlist, também esperando, não pode fazer prefetch. Resultado: três subsistemas independentes serializados através de um único objeto de estado. Custo de desempenho: O(N) contentão de lock onde N é o número de subsistemas, todos proporcional à latência da operação.
Loop de eventos Redis. AOF fsync (gravação no disco), replicação (gravação na rede) e execução de comando (CPU) compartilham o único loop de eventos. Cada um: correto em isolamento. Um fsync lento faz parar a execução de comando. Atraso na replicação agrava sob carga de escrita. O ponto de acoplamento: um único contexto de execução compartilhado por operações com perfis de latência diferentes.
LevelDB VersionSet. Caminho de escrita (flush de memtable) e compactação de fundo compartilham o lock de VersionSet. Um trabalho de compactação mantém o lock por dezenas de milissegundos. Caminho de escrita para para. Ambas as operações: necessárias. O acoplamento: estrutural, não de tempo.
A Distinção Crítica
Um Intertangle tem um acoplamento estrutural, não um problema de tempo. Uma condição de corrida: dois threads acessam estado compartilhado sem sincronização. Correção: adicione um mutex.
Um Intertangle: dois subsistemas compartilham estado por design. Adicionar um mutex não corrige o acoplamento; ele serializa o acesso. Os subsistemas ainda compartilham estado. A garganta se aperta.
Adicionar um mutex a um Intertangle de VLC torna isso pior: agora áudio, vídeo e playlist esperam por um único lock. A correção estrutural: dê a cada subsistema seu próprio estado. Captura de fase: congela um snapshot do estado compartilhado na fronteira de fase, permitindo que cada subsistema leia o snapshot independentemente, mesclando as escritas de volta no final.
Estrutural vs Tempo
A questão diagnóstica chave para um Intertangle: um mutex resolveria o problema ou apenas tornaria tudo pior?
Uma condição de corrida: um mutex resolve o problema. O acesso correto elimina a corrupção.
Um Intertangle: um mutex serializa o acesso, mas preserva o acoplamento estrutural. Os sub-sistemas ainda compartilham estado. Sob carga, eles ainda se bloqueiam. A garganta se estreita.
Como encontrar um Intertangle
Três sinais de detecção:
1. Campos mutáveis compartilhados entre sub-sistemas. Um objeto divino com campos lidos e escritos por mais de um sub-sistema. Se remover o acesso aos campos de um sub-sistema quebrar outro sub-sistema, eles compartilham estado.
2. Mutex único protegendo operações não relacionadas. Um lock protegendo a flush de áudio E a decodificação de vídeo E a recuperação da playlist: três sub-sistemas com diferentes perfis de latência, todos esperando um pelo outro. O cheiro: operações não relacionadas com o mesmo nome de lock.
3. Regressão de desempenho quando a carga aumenta. Latência para a operação A aumenta quando a operação B corre em concorrência, mesmo que A e B pareçam independentes. Eles não são independentes: eles compartilham estado.
A solução de captura de fase
Padrão de captura de fase:
# ANTES: sub-sistemas leem e escrevem diretamente no estado compartilhado
class GameWorld:
position = {} # mutable compartilhado
velocity = {} # mutable compartilhado
def physics_tick(world):
for entity in world.entities:
world.position[entity] += world.velocity[entity] # escreve estado compartilhado no meio do loop
# DEPOIS: snapshot congelado antes da fase; as escritas vão para o buffer next_state
def physics_tick(world):
snapshot = world.freeze() # visualização imutável
next_state = {}
for entity in snapshot.entities:
next_state[entity] = snapshot.position[entity] + snapshot.velocity[entity]
world.merge(next_state) # fusão atômica na fronteira da fase
Cada sub-sistema lê o snapshot. Nenhum sub-sistema escreve nele. As escritas acumulam-se em um buffer e se fundem atomicamente na fronteira da fase. Os sub-sistemas agora executam independentemente: não há contenção de trancas, nenhuma dependência de ordem, nenhuma conexão oculta.
Aplicar a correção
Um time relata um defeito: o motor de jogo de animação e o sistema de colisão ambos escrevem em um objeto transform de entidade compartilhada. Quando ambos correm no mesmo tick, os resultados de colisão dependem de se a animação correu primeiro. Adicionar um mutex corrigiu a ordem, mas agora a animação fica travada sempre que a colisão executa uma varredura de larga fase.