Como um Intertangle se Forma
Dois subsistemas começam como módulos independentes. Com o tempo, cada um acumula um campo em um god-object compartilhado: uma struct de configuração global, um singleton manager, uma classe estática. Cada adição: correta isoladamente. O acoplamento: invisível em testes de pequena escala.
Três substratos onde isso se calcificou:
VLC media player. Áudio, vídeo e playlist compartilham um único lock que protege o estado global do player. Uma solicitação de skip-to-timestamp adquire o lock, modifica a posição de reprodução e esvazia o buffer de áudio. O subsistema de vídeo, aguardando o mesmo lock, fica travado. O subsistema de playlist, também aguardando, não consegue fazer prefetch. Resultado: três subsistemas independentes serializados por meio de um único objeto de estado. Custo de desempenho: contenção de lock O(N), onde N = número de subsistemas, tudo proporcional à latência da operação.
Loop de eventos do Redis. O fsync do AOF (escrita em disco), a replicação (escrita em rede) e a execução de comandos (CPU) compartilham o loop de eventos single-threaded. Cada um: correto isoladamente. Um fsync lento trava a execução de comandos. O lag de replicação piora sob carga de escrita. O ponto de acoplamento: um único contexto de execução compartilhado por operações com perfis de latência diferentes.
VersionSet do LevelDB. O caminho de escrita (flush da memtable) e a compactação em background compartilham o lock do VersionSet. Um job de compactação mantém o lock por dezenas de milissegundos. O caminho de escrita trava. Ambas as operações: necessárias. O acoplamento: estrutural, não temporal.
A Distinção Crítica
Um Intertangle possui um acoplamento estrutural, não um problema de timing. Uma race condition: duas threads acessam estado compartilhado sem sincronização. Solução: adicionar um mutex.
Um Intertangle: dois subsistemas compartilham estado por design. Adicionar um mutex não corrige o acoplamento; apenas serializa o acesso. Os subsistemas ainda compartilham estado. O gargalo piora.
Adicionar um mutex a um Intertangle do VLC piora a situação: agora áudio, vídeo e playlist esperam por um único lock. A correção estrutural: dar a cada subsistema seu próprio estado. Snapshot de fase: congelar um snapshot do estado compartilhado na fronteira de fase, permitir que cada subsistema leia o snapshot independentemente e mesclar as escritas de volta ao final.
Estrutural vs Timing
A pergunta diagnóstica chave para um Intertangle: adicionar um mutex corrige ou piora o problema?
Uma condição de corrida: adicionar um mutex corrige o problema. O ordenamento correto de acesso elimina a corrupção.
Um Intertangle: adicionar um mutex serializa o acesso, mas preserva o acoplamento estrutural. Os subsistemas ainda compartilham estado. Sob carga, eles ainda bloqueiam uns aos outros. O gargalo se estreita.
Como Encontrar um Intertangle
Três sinais de detecção:
1. Campos mutáveis compartilhados entre subsistemas. Um god-object com campos lidos e escritos por mais de um subsistema. Se remover o acesso de um subsistema a um campo quebrar outro subsistema, eles compartilham estado.
2. Um único mutex protegendo operações não relacionadas. Um lock protegendo flush de áudio E decodificação de vídeo E busca de playlist: três subsistemas com perfis de latência diferentes, todos esperando uns pelos outros. O cheiro: operações não relacionadas sob o mesmo nome de lock.
3. Regressão de desempenho ao adicionar carga. A latência da operação A aumenta quando a operação B é executada concorrentemente, mesmo que A e B pareçam independentes. Eles não são independentes: compartilham estado.
A Correção Phase-Snapshot
Padrão phase snapshot:
# ANTES: subsistemas leem e escrevem estado compartilhado diretamente
class GameWorld:
position = {} # estado mutável compartilhado
velocity = {} # estado mutável compartilhado
def physics_tick(world):
for entity in world.entities:
world.position[entity] += world.velocity[entity] # escreve no estado compartilhado durante o loop
# DEPOIS: snapshot congelado antes da fase; escritas vão para o buffer next_state
def physics_tick(world):
snapshot = world.freeze() # visão imutável
next_state = {}
for entity in snapshot.entities:
next_state[entity] = snapshot.position[entity] + snapshot.velocity[entity]
world.merge(next_state) # mesclagem atômica no limite da fase
Todo subsistema lê o snapshot. Nenhum subsistema escreve nele. As escritas acumulam em um buffer e são mescladas atomicamente no limite de fase. Os subsistemas agora executam de forma independente: sem contenção de locks, sem dependência de ordenação, sem acoplamento oculto.
Aplique o Fix
Uma equipe relata um defeito: o sistema de animação e o sistema de colisão do motor de jogo ambos escrevem em um objeto de transformação de entidade compartilhado. Quando ambos executam no mesmo tick, os resultados de colisão dependem de a animação ter executado primeiro. Adicionar um mutex corrigiu a ordenação, mas agora a animação trava sempre que a colisão executa uma varredura de fase ampla.