Testando condições de corrida multithread

54

Lendo os comentários para esta resposta , especificamente:

Só porque você não pode escrever um teste não significa que ele não está quebrado. Comportamento indefinido que geralmente funciona como esperado (C e C ++ estão cheios disso), condições de corrida, reordenação potencial devido a um modelo de memória fraco ... - CodesInChaos 7 horas atrás

@CodesInChaos, se não puder ser reproduzido, o código escrito para 'fixar' também não poderá ser testado. E colocar código não testado em tempo real é um crime pior na minha opinião - RhysW há 5 horas

... está me perguntando se existem boas maneiras gerais de acionar consistentemente, com pouca frequência, problemas de produção causados ​​por condições de corrida no caso de teste.

Dan Neely
fonte
11
passo a (montagem) de instruções pela instrução em ambas as extremidades
anormal de roquete
11
A análise estática pode frequentemente mostrar UB em potencial, não está claro se isso é contado como teste
jk.
Desculpe perguntar, mas o que significa 'UB'?
Doug
2
Boa pergunta, eu seria interessante em ver as possíveis soluções para isso.
RhysW
11
@Doug Comportamento indefinido, que pode incluir, mas não está limitado a, condições de corrida
jk.

Respostas:

85

Depois de estar nesse negócio maluco desde 1978, depois de ter passado quase todo esse tempo em computação em tempo real incorporada, trabalhando em sistemas multitarefa, multithread e multi-seja o que for, às vezes com vários processadores físicos, perseguindo mais do que minha parte justa na corrida condições, minha opinião considerada é que a resposta para sua pergunta é bastante simples.

Não.

Não há uma boa maneira geral de desencadear uma condição de corrida nos testes.

Sua ÚNICA esperança é projetá-los completamente fora do seu sistema.

Quando e se você achar que alguém mais o colocou, você deve colocá-lo no formigueiro e redesenhar para eliminá-lo. Depois de projetar o faux pas (pronuncia-se f *** up) do seu sistema, você pode libertá-lo das formigas. (Se as formigas já o consumiram, deixando apenas ossos, coloque uma placa dizendo "Isto é o que acontece com as pessoas que colocam condições de corrida no projeto XYZ!" E DEIXE-A LÁ.)

John R. Strohm
fonte
22
Eu concordo completamente. Em outras palavras, isso é muito parecido com a piada - Paciente: "Doutor, dói quando faço isso ..." Doutor: "Então pare de fazer isso!"
Mark Rushakoff
Boa resposta. Se algo causar um problema não testável, tente contorná-lo para começar, evite o problema completamente!
RhysW
Minha única pergunta é: Qual o tamanho do formigueiro devo usar? (+1 BTW).
Peter K.
15
+1 para a pronúncia correta de faux pas . (E o resto da resposta.)
Blrfl
11
@PeterK., Esse é um daqueles poucos casos em desenvolvimento de software, junto com monitores, RAM e unidades de disco, onde maior é melhor.
John R. Strohm
16

Se você estiver na cadeia de ferramentas ms. A pesquisa da Sra. Criou uma ferramenta que forçará novos interlevings para cada execução e pode recriar execuções com falha, chamadas de xadrez .

Aqui está um vídeo mostrando isso em uso.

reexecutar
fonte
5
Isso parece impressionante; Vou ter que encontrar tempo para experimentá-lo em algum momento.
precisa
16

A melhor ferramenta que conheço para esse tipo de problema é uma extensão do Valgrind chamada Helgrind .

Basicamente, o Valgrind simula um processador virtual e executa seu binário (não modificado) sobre ele, para que ele possa verificar todos os acessos à memória. Usando essa estrutura, o sistema de observação Helgrind chama para inferir quando um acesso a uma variável compartilhada não é adequadamente protegido por um mecanismo de exclusão mútua. Dessa forma, ele pode detectar uma condição de corrida teórica, mesmo que não tenha realmente acontecido.

A Intel vende uma ferramenta muito semelhante chamada Intel Inspector .

Essas ferramentas oferecem ótimos resultados, mas seu programa será consideravelmente mais lento durante a análise.

Julien
fonte
11
Valgrind ainda é apenas uma ferramenta * nix?
21413 Dan Neely
11
Sim, Linux, MacOSX, android e alguns BSD: valgrind.org/info/platforms.html
Julien
11
ThreadSanitizer é uma ferramenta semelhante. Funciona de maneira diferente do Helgrind, o que lhe dá a vantagem de ser muito mais rápido, mas requer integração ao conjunto de ferramentas.
23813 Sebastian Redl
7

Expor um bug com vários threads exige forçar diferentes threads de execução a executar suas etapas em uma ordem intercalada específica. Normalmente, isso é difícil de fazer sem depurar manualmente ou manipular o código para obter algum tipo de "identificador" para controlar essa intercalação. Mas alterar o código que se comporta de maneira imprevisível geralmente influencia essa imprevisibilidade, portanto, isso é difícil de automatizar.

Um bom truque é descrito por Jaroslav Tulach no Practical API Design : se você tiver instruções de log no código em questão, manipule o consumidor dessas instruções de log (por exemplo, um pseudo-terminal injetado) para que ele aceite as mensagens de log individuais em um determinado pedido com base em seu conteúdo. Isso permite que você controle a intercalação de etapas em diferentes threads sem precisar adicionar nada ao código de produção que ainda não está lá.

Kilian Foth
fonte
2
Eu fiz o mesmo antes de usar o repositório injetado para suspender os threads que o chamam em ordens específicas para forçar a intercalação que eu quero. Tendo escrito o código que faz isso, estou inclinado a marcar a resposta de +1 em John acima. Sério, esse material é tão doloroso de empregar corretamente e ainda oferece apenas as melhores garantias de adivinhação, porque pode haver intercalações ligeiramente diferentes com resultados diferentes; a melhor abordagem é simplesmente eliminar todas as condições de corrida possíveis através da análise estática e ou pentear cuidado de código para todo e qualquer estado compartilhado
Jimmy Hoffa
6

Não há como ter certeza absoluta de que vários tipos de comportamento indefinido (em particular condições de corrida) não existem.

No entanto, existem várias ferramentas que mostram um bom número dessas situações. Você poderá provar que existe atualmente um problema com essas ferramentas, mesmo que não possa provar que sua correção é válida.

Algumas ferramentas interessantes para esse fim:

Valgrind é um verificador de memória. Ele encontra vazamentos de memória, leituras de memória não inicializada, uso de ponteiros pendentes e acessos fora dos limites.

Helgrind é um verificador de segurança de threads. Encontra condições de corrida.

Ambos trabalham com instrumentação dinâmica, ou seja, eles pegam seu programa como estão e o executam em um ambiente virtualizado. Isso os torna intrusivos, mas lentos.

O UBSan é um verificador de comportamento indefinido. Ele encontra vários casos de comportamento indefinido em C e C ++, como estouros de número inteiro, mudanças fora do intervalo e coisas semelhantes.

MSan é um verificador de memória. Tem objetivos semelhantes aos de Valgrind.

TSan é um verificador de segurança de threads. Tem objetivos semelhantes aos do Helgrind.

Esses três são incorporados ao compilador Clang e geram código em tempo de compilação. Isso significa que você precisa integrá-los ao seu processo de compilação (em particular, é necessário compilar com o Clang), o que os torna muito mais difíceis de configurar inicialmente do que o * grind, mas por outro lado, eles têm uma sobrecarga de tempo de execução muito menor.

Todas as ferramentas listadas funcionam no Linux e algumas no MacOS. Eu não acho que nenhum trabalho no Windows ainda seja confiável.

Sebastian Redl
fonte
1

Parece que a maioria das respostas aqui confundem esta pergunta como "como eu detecto automaticamente as condições da corrida?" quando a pergunta é realmente "como reproduzo as condições de corrida nos testes quando as encontro?"

A maneira de fazer isso é introduzir a sincronização no seu código, usada apenas para teste. Por exemplo, se ocorrer uma condição de corrida quando o Evento X acontecer entre o Evento A e o Evento B, para testar seu aplicativo, escreva um código que aguarde o Evento X acontecer após o Evento A. Você provavelmente precisará de alguma maneira para que seus testes conversem com seu aplicativo e diga-o ("ei, estou testando isso, então aguarde esse evento neste local").

Estou usando o node.js e o mongo, onde algumas ações envolvem a criação de dados consistentes em várias coleções. Nesses casos, meus testes de unidade farão uma chamada para o aplicativo dizendo "configure uma espera para o evento X" e, depois que o aplicativo o configurar, o teste para o evento X será executado e os testes informarão posteriormente o aplicativo ("terminei com a espera do evento X") para que o restante dos testes seja executado normalmente.

A resposta aqui explica esse tipo de coisa em detalhes no contexto do python: https://stackoverflow.com/questions/19602535/how-can-i-reproduce-the-race-conditions-in-this-python-code- confiável

BT
fonte