antonio leandro

estruturas e algoritmos

C/C++ Thread Safety Analysis

paper · DeLesley Hutchins, Aaron Ballman, Dean Sutherland ·

a tese

corrida de dados vira erro de tipo: se o programador declara qual lock protege qual dado, o compilador cobra a disciplina — e o custo de anotar, o argumento clássico contra isso, não impediu a adoção

o que fica

  1. A análise trata segurança de thread como sistema de tipos: a política de locking vira parte da declaração do dado, e o compilador verifica se o código a respeita.
  2. A ferramenta não descobre a política sozinha — alguém precisa escrever qual lock protege o quê, e essa continua sendo uma decisão de projeto humana.
  3. Por ser estática, a análise aponta corrida e deadlock potenciais a partir da política declarada, sem depender de o teste ter tido o azar de pegar o agendamento ruim.
  4. O custo de anotar, que era o principal argumento contra esse tipo de ferramenta, não virou passivo na prática: a adoção no Google foi voluntária e em larga escala.
  5. As anotações funcionam como documentação executável: quando alguém muda a disciplina de locking, o compilador reclama, o que ajuda na evolução e na manutenção do código.
  6. Morar dentro de um compilador de produção, atrás de uma flag de warning, resolve o problema de distribuição que mata a maioria das ferramentas de análise estática avulsas.

o problema

Programa multithread em C e C++ tem uma política que quase nunca está no código. O invariante real é do tipo “este campo só pode ser lido com este mutex segurado”, “esta função assume que o chamador já pegou o lock”, “estes dois locks se tomam sempre nesta ordem”. Nada disso é expresso na linguagem. Vive em comentário, em convenção de time, na cabeça de quem escreveu — e some quando essa pessoa muda de projeto.

O resultado é a classe de bug que mais castiga: a corrida de dados não falha de forma reproduzível. Ela depende do agendamento, do número de núcleos, da carga do dia. Um teste que passa mil vezes não prova nada; um deadlock por ordem de aquisição invertida pode ficar dormindo até o tráfego dobrar. Existiam ferramentas para atacar isso, mas o histórico do campo é de baixa adoção: ou pedem anotação (e o time reclama do custo), ou tentam inferir tudo (e afogam o desenvolvedor em falso positivo), ou rodam fora do build (e ninguém roda).

a ideia

O movimento central é reclassificar o problema. Corrida de dados deixa de ser um evento de execução, a ser caçado com instrumentação e sorte, e passa a ser um erro de tipo — algo que o compilador detecta olhando o texto do programa, antes de qualquer coisa rodar.

Para isso, a política de threading precisa ser dita em voz alta. O programador anota: este dado é guardado por aquele lock, esta função exige aquele lock segurado. As anotações não mudam o que o programa faz; elas dão ao compilador o vocabulário para verificar se cada acesso respeita o que foi declarado. É o mesmo contrato que já existia no comentário, só que agora mecanicamente checado.

A segunda metade da ideia é de distribuição, e conta tanto quanto a primeira. A análise foi colocada dentro do Clang, um compilador de produção disponível na maioria das plataformas, e ligada por uma flag de warning: -Wthread-safety. Não é um passo extra no pipeline, não é um binário para alguém lembrar de invocar. É mais um warning no build que o time já roda. Ferramenta de análise estática costuma morrer por atrito de adoção, e esse desenho remove quase todo o atrito.

o que isso custou

A anotação. Alguém tem que escrevê-la, e o compilador não adivinha a política — ele confere a política que você declarou. Código não anotado não é verificado, o que significa que o valor da análise cresce devagar, arquivo por arquivo, e que uma base legada grande é um trabalho de migração, não um flip de flag. Essa exigência era vista como a falha fatal da abordagem, e o paper trata disso de frente: na prática, no uso em larga escala no Google, a necessidade de anotar não foi um passivo — e ainda trouxe benefício para evolução e manutenção, porque a política escrita como código não apodrece em silêncio como apodrece o comentário.

Também vale ser exato sobre o que a análise promete: detectar corrida e deadlock potenciais. Isso é diferente de provar que o programa não tem nenhum. O que ela garante é coerência com o que foi declarado; uma política declarada errado é verificada com o mesmo rigor. Este verbete foi escrito a partir do resumo do paper, que não quantifica falso positivo, custo de compilação nem cobertura — para isso, o texto completo.

onde isso aparece hoje

A análise continua no Clang, ligável em qualquer build com a mesma flag, e o estilo de anotação virou prática comum em bases C++ grandes. É a linhagem antiga de restringir o que o programador pode escrever para que a máquina consiga checar, a mesma que Go To Statement Considered Harmful inaugurou: abrir mão de liberdade em troca de verificabilidade.

O contraste com o que veio depois é o mais útil para quem escreve Rust ou Go hoje. Rust levou a mesma aposta ao extremo oposto do espectro de opt-in: a disciplina de acesso concorrente não é anotação voluntária, é o sistema de tipos da linguagem, e não compilar é o comportamento padrão. Go foi pelo outro caminho, o dinâmico, com detector de corrida em tempo de execução — que só vê o que a execução tocou. O paper do Clang é o ponto do meio: linguagem que não te ajuda, mais um contrato opcional que, quando você o escreve, o compilador passa a cobrar.

lido pelo resumo por pipeline de llm, revisado por antonio leandro antes de publicar ·