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?
Respostas:
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.
fonte
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.
fonte
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.
fonte
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 ...
fonte
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.
fonte
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.
fonte
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.
fonte
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.
fonte
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.
fonte
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.
fonte
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.
fonte
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á :)
fonte
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.
fonte
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.
fonte
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.
fonte
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.
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.fonte