Devo cuidar de condições de corrida que quase certamente não têm chance de ocorrer?

52

Vamos considerar algo como um aplicativo de GUI no qual o thread principal está atualizando a UI quase instantaneamente, e algum outro thread está realizando polling de dados pela rede ou algo que pode levar de 5 a 10 segundos para terminar o trabalho.

Eu recebi muitas respostas diferentes para isso, mas algumas pessoas dizem que, se é uma condição de corrida de uma impossibilidade estatística, não se preocupe com isso em tudo, mas outros disseram que, se houver um 10 -53 % (Eu não estou brincando com os números, isso é o que eu ouvi) de alguma mágica de vodu acontecendo devido a condição de corrida, sempre obtendo / liberando bloqueios no encadeamento que precisa dela.

Quais são seus pensamentos? É uma boa prática de programação lidar com condições de corrida em situações tão estatisticamente impossíveis? ou seria totalmente desnecessário ou mesmo contraproducente adicionar mais linhas de código para dificultar a legibilidade?

    
por l46kok 10.08.2015 / 00:38
fonte

16 respostas

136

Se for realmente um evento 1 em 10 ^ 55, não haverá necessidade de codificá-lo. Isso implicaria que se você fizesse a operação 1 milhão de vezes por segundo, você receberia um bug a cada 3 * 10 ^ 41 anos, que é, aproximadamente, 10 ^ 31 vezes a idade do universo. Se o seu aplicativo tiver um erro apenas uma vez em cada trilhão de trilhão de bilhões de anos do universo, provavelmente é confiável o suficiente.

No entanto, eu apostaria strongmente que o erro não é nem de longe tão improvável. Se você puder conceber o erro, é quase certo que ele ocorrerá pelo menos ocasionalmente, fazendo com que valha a pena codificar corretamente para começar. Além disso, se você codificar os threads corretamente no início para que eles obtenham e liberem os bloqueios adequadamente, o código será muito mais fácil de manter no futuro. Você não precisa se preocupar quando está fazendo uma mudança, você tem que re-analisar todas as possíveis condições de corrida, re-computar suas probabilidades e assegurar-se de que elas não se repitam.

    
por 17.08.2012 / 05:47
fonte
69

Do ponto de vista de custo-benefício, você deve escrever um código adicional apenas quando você recebe benefícios suficientes.

Por exemplo, se a pior coisa que aconteceria se um tópico errado "ganhasse a corrida" fosse que as informações não fossem exibidas, e o usuário precisasse clicar em "atualizar", não se incomodaria em se proteger contra a condição de corrida : ter que escrever um monte de código não vale a pena consertar algo que é insignificante.

Por outro lado, se a condição de corrida puder resultar em transferências de dinheiro incorretas entre contas bancárias, você deve se proteger contra a condição de corrida, não importando o quanto de código você precise escrever para resolver esse problema.

    
por 17.08.2012 / 05:50
fonte
45

Encontrar uma condição de corrida é a parte difícil. Você provavelmente gastou tanto tempo escrevendo essa pergunta quanto teria levado você para consertá-la. Não é como isso torna muito menos legível. Os programadores esperam para ver o código de sincronização em tais situações e, na verdade, podem desperdiçar mais tempo perguntando por que ele não está lá e se adicioná-lo corrigiria o erro não relacionado.

No que diz respeito a probabilidades, você ficaria surpreso. Eu tive um relatório de bug de condição de corrida no ano passado que não consegui reproduzir com milhares de tentativas automatizadas, mas um sistema de um cliente viu isso o tempo todo. O valor comercial de gastar 5 minutos para corrigi-lo agora, em vez de possivelmente solucionar um erro "impossível" na instalação de um cliente, torna a escolha óbvia.

    
por 17.08.2012 / 06:37
fonte
27

Obtenha e libere os bloqueios. As probabilidades mudam, os algoritmos mudam. É um mau hábito entrar, e quando algo dá errado você não precisa parar e se perguntar se você entendeu errado ...

    
por 17.08.2012 / 05:53
fonte
13

and some other thread is polling data over the network or something that is guaranteed to take 5-10 seconds to finish the job.

Até que alguém introduza uma camada de cache para melhorar o desempenho. De repente, esse outro passo terminou quase instantaneamente e a condição de corrida se manifesta com mais frequência do que não.

Isso aconteceu exatamente há algumas semanas e demorou cerca de dois dias completos de desenvolvimento para encontrar o bug.

Sempre conserte as condições de corrida se você as reconhecer.

    
por 17.08.2012 / 15:12
fonte
8

Simples vs correto.

Em muitos casos, a simplicidade supera a correção. É uma questão de custo.

Além disso, as condições de corrida são coisas desagradáveis que tendem a não obedecer a estatísticas simples. Tudo vai bem até que alguma outra sincronização aparentemente não relacionada faça com que sua condição de corrida aconteça repentinamente na metade do tempo. A menos que você ative os registros ou depure o código, é claro.

Uma alternativa pragmática para prevenir uma condição de corrida (que pode ser complicada) pode ser detectá-lo e registrá-lo (bônus por falhar com força e cedo). Se isso nunca acontecer, você perdeu pouco. Se isso realmente acontecer, você tem uma justificativa sólida para gastar o tempo extra consertando-o.

    
por 17.08.2012 / 06:46
fonte
7

Se a sua condição de corrida for relacionada à segurança, você deve sempre codificar para evitar isso.

Um exemplo comum são as condições de corrida com a criação / abertura de arquivos no unix, o que pode levar a ataques de escalonamento de privilégios se o programa com a condição de corrida estiver sendo executado com privilégios maiores do que o usuário interagindo com ele. processo daemon ou, pior ainda, o kernel.

Mesmo que uma condição de corrida tenha algo como 10 ^ (- 80) chance de acontecer aleatoriamente , pode ser que um atacante determinado tenha uma boa chance de criar tais condições deliberada e artificialmente .

    
por 17.08.2012 / 13:30
fonte
6

Therac-25!

Os desenvolvedores do projeto Therac-25 estavam bastante confiantes sobre o tempo entre uma interface do usuário e um problema relacionado à interface em uma máquina XRAY terapêutica.

Eles não deveriam ter sido.

Você pode aprender mais sobre esse famoso desastre de software de vida e morte em:

link

ou

link

Seu aplicativo pode ser muito menos sensível a falhas do que os dispositivos médicos. Um método útil é avaliar a exposição ao risco como o produto da probabilidade de ocorrência e o custo de ocorrência durante a vida útil do produto para todas as unidades que poderiam ser produzidas.

Se você optou por construir seu código para durar (e parece que você tem), você deve considerar a lei de Moore que pode facilmente eliminar vários zeros a cada poucos anos à medida que os computadores dentro ou fora do sistema ficam mais rápidos. Se você enviar milhares de cópias, elimine mais zeros. Se os usuários fizerem essa operação diariamente (ou mensalmente) por anos, retire mais alguns. Se for usado onde a fibra do Google está disponível, então? Se o lixo da interface do usuário coleta a operação média da GUI, isso afeta a corrida? Você está usando uma biblioteca Open Source ou Windows por trás de sua GUI? As atualizações podem afetar o tempo?

Semáforos, bloqueios, exclusões mútuas, sincronização de barreira estão entre as formas de sincronizar atividades entre os encadeamentos. Potencialmente, se você não estiver usando-os, outra pessoa que mantém seu programa pode e, muito rapidamente, suposições sobre relacionamentos entre threads podem mudar e o cálculo sobre a condição de corrida pode ser invalidado.

Eu recomendo que você sincronize explicitamente porque, embora você possa nunca ver isso criando um problema, um cliente pode. Além disso, mesmo que sua condição de corrida nunca ocorra, e se você ou sua organização forem chamados à Justiça para defender seu código (como a Toyota estava relacionada ao Prius há alguns anos atrás). Quanto mais meticulosa for sua metodologia, melhor você se sairá. Pode ser melhor dizer "nós nos protegemos contra esse caso improvável como este ..." do que dizer "sabemos que nosso código falhará, mas escrevemos essa equação para mostrar que isso não acontecerá em nossa vida. Provavelmente. "

Parece que o cálculo da probabilidade vem de outra pessoa. Eles conhecem seu código e você os conhece o suficiente para confiar que nenhum erro foi cometido? Se eu calculasse uma confiabilidade de 99,99997% para algo, eu também poderia pensar em minhas aulas de estatística da faculdade e lembrar que nem sempre obtive 100%, e recuo um pouco em minhas próprias estimativas de confiabilidade pessoal.

    
por 21.08.2012 / 05:17
fonte
4

would it be totally unnecessary or even counterproductive to add more lines of code to hinder readability?

A simplicidade só é boa quando está correta. Como esse código não está correto, futuros programadores irão inevitavelmente olhar para ele quando procurar por um bug relacionado.

Seja qual for a forma como você lida com isso (registrando, documentando ou adicionando os bloqueios - isso depende do custo), você economizará tempo de outros programadores ao olhar para o código.

    
por 17.08.2012 / 16:26
fonte
3

Isso dependeria do contexto. Se é um jogo casual de iPhone, provavelmente não. O sistema de controle de vôo para o próximo veículo espacial tripulado, provavelmente. Tudo depende de quais são as consequências se o resultado "ruim" for medido em relação ao custo estimado de consertá-lo.

Raramente há uma resposta 'tamanho único' para esses tipos de perguntas porque elas não são questões de programação, mas sim questões econômicas.

    
por 17.08.2012 / 05:54
fonte
3

Sim, espere o inesperado. Eu passei horas (em outras pessoas codificam ^^) rastreando condições que nunca deveriam acontecer.

Coisas como sempre tem um else, sempre tem um default no caso, inicialize variáveis (sim, realmente .. bugs acontecem a partir disso), verifique seus loops para variáveis reutilizadas para cada iteração, etc.

Se você está preocupado em abordar questões especificamente, leia blogs, artigos e livros sobre o assunto. O tema atual parece ser um dado imutável.

    
por 17.08.2012 / 17:09
fonte
3

Apenas conserte.

Eu vi exatamente isso. Um thread consegue fazer uma solicitação de rede para um servidor que faz uma consulta complexa ao banco de dados e responde antes que o outro thread chegue à próxima linha de código. Acontece.

Algum cliente em algum lugar decidirá um dia para executar algo que ocupa todo o tempo da CPU para o thread "rápido" enquanto deixa o thread lento em execução, e você vai se arrepender:)

    
por 17.08.2012 / 17:48
fonte
1

Se você reconheceu uma condição improvável de corrida, pelo menos registre-a no código!

EDIT: Devo acrescentar que eu consertaria isso se fosse possível, mas no momento em que escrevo o texto acima, nenhuma outra resposta explicitamente disse, pelo menos, documentar o problema no código.

    
por 22.08.2012 / 09:01
fonte
0

Eu acho que se você já sabe como e por que isso poderia acontecer, também poderia lidar com isso. Isto é, se não ocupar uma grande quantidade de recursos.

    
por 17.08.2012 / 15:07
fonte
0

Tudo depende das conseqüências de uma condição de corrida. Eu acho que as pessoas que respondem à sua pergunta estão corretas para o seu trabalho. O meu é os motores de configuração do roteador. Para mim, as condições de corrida fazem com que os sistemas fiquem parados, corrompidos ou desconfigurados, apesar de dizerem que foi bem-sucedido. Eu sempre uso semáforos por roteador para não precisar limpar nada à mão.

Acho que parte do meu código GUI ainda é propenso a condições de corrida de tal forma que um usuário pode receber um erro porque uma condição de corrida aconteceu, mas eu não teria essas possibilidades se houvesse uma chance de corrupção de dados ou mau comportamento da aplicação após esse evento.

    
por 18.08.2012 / 02:34
fonte
0

Curiosamente, encontrei esse problema recentemente. Eu nem percebi que uma condição de corrida era possível na minha situação. A condição de corrida só se apresentou quando os processadores multi-core se tornaram a norma.

O cenário foi mais ou menos assim. Um driver de dispositivo gerou eventos para o software manipular. O controle precisava retornar ao driver do dispositivo o mais rápido possível para evitar um tempo limite no dispositivo. Para garantir isso, o evento foi gravado e enfileirado em um segmento separado.

Receive event from device:
{
    Record event details.
    Enqueue event in the queuing thread.
    Acknowledge the event.
}

Queueing thread receives an event:
{
    Retrieve event details.
    Process event.
    Send next command to device.
}

Isso funcionou bem por anos. Então, de repente, falharia em certas configurações. Acontece que o encadeamento de filas agora estava sendo executado de forma verdadeiramente paralela ao segmento de tratamento de eventos, em vez de compartilhar o tempo de um único processador. Ele conseguiu enviar o próximo comando para o dispositivo antes que o evento fosse reconhecido, causando um erro fora de seqüência.

Dado que só afetou um cliente em uma configuração, eu coloquei vergonhosamente um Thread.Sleep(1000) em onde estava o problema. Não tem havido um problema desde então.

    
por 18.08.2012 / 13:01
fonte