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 em que o thread principal está atualizando a interface do usuário quase instantaneamente, e algum outro thread está pesquisando dados pela rede ou algo com garantia de 5 a 10 segundos para concluir o trabalho.

Recebi muitas respostas diferentes para isso, mas algumas pessoas dizem que, se é uma condição de corrida com uma impossibilidade estatística, não se preocupe, mas outras disseram que, se houver entre 10 e 53 % (eu brinco você não está nos números, foi o que ouvi falar) de alguma mágica vodu que ocorre devido à condição de corrida, sempre obtenha / libere bloqueios no fio que precisa.

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

l46kok
fonte
21
Quando as pessoas estão afirmando chances como essa, por que ninguém pergunta sobre a educação de uma pessoa informando esse número? Você precisa de uma educação formal em estatística antes de poder fazer backup com um número como esse.
Pieter B
27
Como físico, p <1E-140 significa p = 0. Não vai acontecer neste universo. 0.00000000000000000000000000000000000000000000000000001001% é muito maior.
MSalters
15
Certifique-se de que essa condição de corrida não leve a alguém de boa vontade travando seu aplicativo. Isso pode ser a causa de um problema de segurança.
toasted_flakes
27
Uma em um milhão de chances acontece nove em cada dez.
Kaz Dragon
27
"quase certamente não tem chance de acontecer?" significa que acontece na produção às 3 da manhã e provavelmente será muito caro.

Respostas:

137

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

No entanto, eu apostaria fortemente que o erro não é nem de longe improvável. Se você pode conceber o erro, é quase certo que ele ocorra pelo menos ocasionalmente, fazendo com que valha a pena codificar corretamente para começar. Além disso, se você codificar os threads corretamente desde o início, para que eles obtenham e liberem bloqueios adequadamente, o código será muito mais sustentável no futuro. Ao fazer uma alteração, você não precisa se preocupar em analisar novamente todas as condições de corrida em potencial, recalcular suas probabilidades e garantir a si mesmo que elas não se repetirão.

Justin Cave
fonte
66
Lembro-me de um comentário que li anos atrás, mas não consigo encontrar agora "A chance de 1 em um milhão é geralmente na próxima terça-feira". +1 por dizer que "não é nem de longe improvável".
Bevan
2
+1 para a aposta. A melhor maneira de lidar com as condições da corrida é se livrar delas.
Blrfl
10
@Bevan "A 1 chance em um milhão é geralmente próxima terça-feira" ... a menos que você está jogando uma loteria :)
dasblinkenlight
22
@dasblinkenlight Mas as chances de alguém ganhar na maioria das loterias se aproxima de 100%. Prevendo quem , agora esse é o desafio.
Bevan
3
@Bevan: Esse comentário foi exatamente o que estava passando pela minha mente quando li a pergunta - aqui está a referência: blogs.msdn.com/b/larryosterman/archive/2004/03/30/104165.aspx
Doc Brown
69

Do ponto de vista de custo-benefício, você deve escrever código adicional apenas quando obtiver benefício suficiente.

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

Por outro lado, se a condição de corrida puder resultar em transferências incorretas de dinheiro entre contas bancárias, você deverá se proteger contra a condição de corrida, independentemente do código necessário escrever para resolver esse problema.

dasblinkenlight
fonte
20
+1: Para fazer a distinção entre "Falha que parece falha" e "Falha que parece sucesso". Informações incorretas são muito mais graves, dependendo do domínio.
deworde
2
+1 faz uma grande diferença quais poderiam ser os resultados da condição de corrida.
Grant
+1 A conseqüência da condição de corrida deve ser um fator decisivo importante se for abordada. Uma condição de corrida que pode causar um acidente de avião é muito diferente de uma condição que pode forçar o usuário a reabrir um aplicativo.
Poke
11
+1: Eu diria que as consequências são provavelmente o que você deve analisar e não a probabilidade de isso ocorrer. Se as consequências não importarem, talvez você não precise lidar com a condição de corrida, mesmo que seja muito comum.
Leo
11
Mas não assuma que a correção automática de uma condição de corrida significa que você precisa escrever mais código. Isso pode significar remover um grande pedaço de código de buggy e substituí-lo por um pequeno pedaço de código correto.
precisa saber é o seguinte
45

Encontrar uma condição de corrida é a parte mais difícil. Você provavelmente gastou quase tanto tempo escrevendo essa pergunta quanto seria necessário para corrigi-la. Não é como se isso fosse muito menos legível. Os programadores esperam ver o código de sincronização em tais situações e, na verdade, podem perder mais tempo se perguntando por que ele não está lá e se a adição dele corrigiria o erro não relacionado.

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

Karl Bielefeldt
fonte
11
Isto também! Evite que outros programadores ponderem sobre possíveis problemas ao ler seu código, fazendo o que é necessário (mesmo que seja "improvável" que falhe).
Casey Kuball
Seu ponto de vista é bem aceito (as correções feitas agora são mais rápidas e mais baratas que as feitas posteriormente), exceto que nunca serão apenas "5 minutos para corrigi-lo agora".
Iconoclast
2
+1 por apontar que a probabilidade da condição de corrida provavelmente depende de muitos fatores; portanto, mesmo que pareça improvável na sua configuração, isso pode ocorrer com mais frequência em um sistema do cliente / em um SO diferente / na próxima versão etc.
sleske 17/08/12
27

Obtenha e solte 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 está com as probabilidades erradas ...

jmoreno
fonte
6
+1 para alteração de algoritmos. No momento, quando você está ciente da condição de corrida, as probabilidades são baixas. Após um ano, quando você se esquecer da condição de corrida, poderá fazer uma alteração no seu código, o que altera significativamente o tempo e a probabilidade de um bug.
21412 Phil
13

e algum outro encadeamento está pesquisando dados pela rede ou algo que é garantido em 5 a 10 segundos para concluir o trabalho.

Até que alguém introduza uma camada de cache para melhorar o desempenho. De repente, outro passo terminou quase instantâneo e a condição de corrida se manifesta mais frequentemente do que não.

Exatamente isso aconteceu há algumas semanas, levou cerca de 2 dias completos para o desenvolvedor para encontrar o bug.

Sempre corrija as condições da corrida se você as reconhecer.

Michael Borgwardt
fonte
8

Simples vs correto.

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

Além disso, condições de corrida são coisas desagradáveis ​​que tendem a não obedecer a estatísticas simples. Tudo vai bem até que 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 logs ou depure o código, é claro.

Uma alternativa pragmática à prevenção de uma condição de corrida (que pode ser complicada) pode ser detectá-la e registrá-la (bônus por falhar forte e cedo). Se isso nunca acontecer, você perderá pouco. Se isso realmente acontecer, você terá uma justificativa sólida para gastar o tempo extra consertando-o.

ptyx
fonte
11
+1 para registro e falha antecipada se a correção definitiva for muito complicada.
Martin Ba
Em muitos casos, a simplicidade supera a perfeição. A sincronização quase nunca está entre esses casos. Quase sempre volta a morder você (ou o pobre sujeito encarregado de manter seu código) mais tarde.
reirab 30/09/14
@reirab Eu discordo. Se você considerar eventos pouco frequentes, a falha registrada é econômica. Um exemplo: se o seu aplicativo de telefone tiver uma taxa de falha de 1/100 (falha) se o usuário estiver trocando de rede em uma transição exata do mês (1/31 23:59:00 -> 2/1 00:00:00), você provavelmente nunca ouvirá falar sobre isso. Mas uma chance de 10/10 ^ 9 de falha na conexão com um servidor é inaceitável. Depende.
Ptyx
7

Se sua condição de corrida estiver relacionada à segurança, você deve sempre codificar para evitá-la.

Um exemplo comum são as condições de corrida com a criação / abertura de arquivos no unix, que em algumas circunstâncias podem levar a ataques de escalonamento de privilégios se o programa com a condição de corrida estiver executando com privilégios mais altos do que o usuário interagindo com ele, como um processo de daemon do sistema ou pior ainda, o kernel.

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

Bristol
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 terapêutica XRAY.

Eles não deveriam ter sido.

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

http://www.youtube.com/watch?v=izGSOsAGIVQ

ou

http://en.wikipedia.org/wiki/Therac-25

Seu aplicativo pode ser muito menos sensível a falhas do que os dispositivos médicos. Um método útil é classificar a exposição ao risco como o produto da probabilidade de ocorrência e o custo da ocorrência ao longo da 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), considere a lei de Moore que pode facilmente cortar 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, corte mais zeros. Se os usuários fizerem essa operação diariamente (ou mensalmente) por anos, tire mais alguns. Se for usado onde a fibra do Google estiver disponível, o que acontecerá? Se o lixo da interface do usuário coletar a operação da GUI no meio, isso afeta a corrida? Você está usando uma biblioteca Open Source ou Windows atrás da sua GUI? As atualizações podem afetar o tempo?

Semáforos, bloqueios, mutexes, sincronização de barreira estão entre as maneiras de sincronizar as atividades entre os threads. Potencialmente, se você não os estiver usando, outra pessoa que mantém seu programa pode, e então rapidamente, suposições sobre relacionamentos entre encadeamentos 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 vê-lo criar 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 a tribunal para defender seu código (como a Toyota estava relacionada ao Prius há alguns anos). Quanto mais completa sua metodologia, melhor você se sairá. Pode ser melhor dizer "nos protegemos contra esse caso improvável como esse ..." do que dizer "sabemos que nosso código falhará, mas escrevemos essa equação para mostrar que não acontecerá em nossa vida. Provavelmente. "

Parece que o cálculo de probabilidade vem de outra pessoa. Eles conhecem o seu código e você o conhece o suficiente para confiar que nenhum erro foi cometido? Se eu calculei uma confiabilidade de 99,999997% para alguma coisa, também devo pensar nas aulas de estatística da faculdade e lembrar que nem sempre recebi 100% e recuo alguns percentuais em minhas próprias estimativas de confiabilidade pessoais.

DesenvolvedorDon
fonte
11
+1 por menção ao Therac-25. Muitas lições importantes aqui.
Stuart Marcas
Embora eu ache que essa seja uma boa resposta, você pode argumentar que seu projeto GUI de hobby certamente não fará as pessoas morrerem se você não conseguir eliminar uma condição de corrida.
marktani
Não sou muito a favor de argumentar, mas, se fosse, poderia argumentar que, sempre que escrevemos código, devemos escrevê-lo corretamente. Se pudermos praticar a obtenção das condições de corrida de nossos projetos de hobby, onde o código é mais simples e talvez sejamos o único autor, estaremos muito mais prontos quando abordarmos projetos de trabalho em que o trabalho de vários autores precisa ser integrado.
DeveloperDon
4

seria totalmente desnecessário ou até contraproducente adicionar mais linhas de código para dificultar a legibilidade?

A simplicidade só é boa quando também está correta. Como esse código não está correto, os programadores futuros o procurarão inevitavelmente ao procurar um bug relacionado.

Qualquer que seja a forma como você lida com isso (registrando-o, documentando-o ou adicionando os bloqueios - isso depende do custo), você economizará tempo de outros programadores ao examinar o código.

Casey Kuball
fonte
3

Isso dependeria do contexto. Se é um jogo casual para 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" acontecer medido em relação ao custo estimado para corrigi-lo.

Raramente existe uma resposta única para esses tipos de perguntas, porque elas não são perguntas de programação, mas questões econômicas.

GrandmasterB
fonte
3
"O sistema de controle de vôo para o próximo veículo espacial tripulado" DEFINITIVAMENTE .
deworde
provavelmente ... definitivamente ... ele iria depender de quem estava no foguete :-)
GrandmasterB
3

Sim, espere o inesperado. Passei horas (no código de outras pessoas ^^) rastreando condições que nunca deveriam acontecer.

Coisas como sempre tem um outro, sempre tem um caso padrão, inicializa variáveis ​​(sim, realmente ... erros acontecem com isso), verifique seus loops para variáveis ​​reutilizadas para cada iteração, etc.

Se você estiver preocupado com problemas de segmentação, leia blogs, artigos e livros sobre o assunto. O tema atual parece ser dados imutáveis.

Paulo
fonte
3

Apenas conserte.

Eu já vi exatamente isso. Um thread consegue fazer uma solicitação de rede para um servidor que faz uma pesquisa complexa no 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 executar algo que consome todo o tempo da CPU para o segmento "rápido" enquanto deixa o segmento lento em execução, e você se arrependerá :)

JohnB
fonte
1

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

EDIT: devo acrescentar que eu o corrigiria, se possível, mas, no momento da redação do texto acima, nenhuma outra resposta disse explicitamente pelo menos documentar o problema no código.

Mark Hurd
fonte
11
Sim, e pelo menos tente detectá-lo e registre-o se isso acontecer. IMHO é perfeitamente bom não evitar todos os erros. Mas pelo menos deixe alguém saber que isso ocorreu e que sua suposição de que isso não ocorreu foi equivocada.
22412 Steve Bennett
0

Eu acho que se você já sabe como e por que isso pode acontecer, é melhor lidar com isso. Ou seja, se não consumir uma quantidade abundante de recursos.

Sjaak van der Heide
fonte
0

Tudo depende de quais são as consequências de uma condição de corrida. Acho que as pessoas que estão respondendo à sua pergunta estão corretas para a sua linha de trabalho. O meu é um mecanismo de configuração de roteador. Para mim, as condições de corrida ou deixam os sistemas parados, corrompidos ou desconfigurados, mesmo que tenham sido bem-sucedidos. Eu sempre uso semáforos por roteador para não precisar limpar nada manualmente.

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

Sylwester
fonte
0

Curiosamente, eu encontrei esse problema recentemente. Eu nem percebi que uma condição de corrida era possível na minha circunstância. A condição de corrida só se apresentou quando os processadores com vários núcleos se tornaram a norma.

O cenário foi mais ou menos assim. Um driver de dispositivo gerou eventos para o software manipular. O controle teve que retornar ao driver do dispositivo o mais rápido possível para evitar um tempo limite no dispositivo. Para garantir isso, o evento foi registrado e enfileirado em um thread 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, ele falharia em certas configurações. Acontece que o encadeamento em fila agora estava sendo executado verdadeiramente em paralelo ao encadeamento de manipulação de eventos, em vez de compartilhar o tempo de um único processador. Ele conseguiu enviar o próximo comando para o dispositivo antes do reconhecimento do evento, causando um erro fora de sequência.

Como afetou apenas um cliente em uma configuração, vergonhosamente coloquei Thread.Sleep(1000)onde estava o problema. Não houve um problema desde então.

Mão-E-Comida
fonte