Ao escrever aplicativos multithread, um dos problemas mais comuns enfrentados são as condições de corrida.
Minhas perguntas à comunidade são:
Qual é a condição da corrida?
Como você os detecta?
Como você lida com eles?
Por fim, como você evita que elas ocorram?
multithreading
concurrency
terminology
race-condition
bmurphy1976
fonte
fonte
Respostas:
Uma condição de corrida ocorre quando dois ou mais encadeamentos podem acessar dados compartilhados e eles tentam alterá-los ao mesmo tempo. Como o algoritmo de agendamento de encadeamentos pode alternar entre encadeamentos a qualquer momento, você não sabe a ordem em que os encadeamentos tentarão acessar os dados compartilhados. Portanto, o resultado da alteração nos dados depende do algoritmo de agendamento de threads, ou seja, os dois threads estão "correndo" para acessar / alterar os dados.
Os problemas geralmente ocorrem quando um thread faz um "check-then-act" (por exemplo, "check" se o valor é X, então "age" para fazer algo que depende do valor que é X) e outro thread faz algo com o valor em entre o "cheque" e o "ato". Por exemplo:
O ponto é que y pode ser 10 ou pode ser qualquer coisa, dependendo se outro segmento alterou x entre a verificação e a ação. Você não tem uma maneira real de saber.
Para impedir que as condições de corrida ocorram, você normalmente bloqueia os dados compartilhados para garantir que apenas um encadeamento possa acessar os dados por vez. Isso significaria algo como isto:
fonte
Existe uma "condição de corrida" quando o código multithread (ou paralelo) que acessaria um recurso compartilhado poderia fazê-lo de maneira a causar resultados inesperados.
Veja este exemplo:
Se você tivesse 5 threads executando esse código de uma vez, o valor de x NÃO acabaria sendo 50.000.000. De fato, variaria a cada execução.
Isso ocorre porque, para cada thread aumentar o valor de x, eles precisam fazer o seguinte: (simplificado, obviamente)
Qualquer encadeamento pode estar em qualquer etapa deste processo a qualquer momento e pode se interpor quando um recurso compartilhado está envolvido. O estado de x pode ser alterado por outro encadeamento durante o tempo em que x está sendo lido e quando é gravado novamente.
Digamos que um thread recupere o valor de x, mas ainda não o armazenou. Outro encadeamento também pode recuperar o mesmo valor de x (porque nenhum encadeamento o alterou ainda) e, em seguida, ambos armazenariam o mesmo valor (x + 1) em x!
Exemplo:
As condições de corrida podem ser evitadas empregando algum tipo de mecanismo de bloqueio antes do código que acessa o recurso compartilhado:
Aqui, a resposta sai sempre como 50.000.000.
Para obter mais informações sobre bloqueio, procure por: mutex, semáforo, seção crítica, recurso compartilhado.
fonte
Você planeja ir ao cinema às 17h. Você pergunta sobre a disponibilidade dos ingressos às 16:00. O representante diz que eles estão disponíveis. Você relaxa e chega à bilheteria 5 minutos antes do show. Tenho certeza que você pode adivinhar o que acontece: é uma casa cheia. O problema aqui estava na duração entre a verificação e a ação. Você perguntou às 4 e agiu às 5. Enquanto isso, outra pessoa pegou os ingressos. Essa é uma condição de corrida - especificamente um cenário de "verificar e agir" das condições de corrida.
Revisão de código religioso, testes de unidade multithread. Não há atalho. Existem poucos plug-ins do Eclipse emergentes nisso, mas nada estável ainda.
O melhor seria criar funções livres e sem estado de efeitos colaterais, usar imutáveis o máximo possível. Mas isso nem sempre é possível. Portanto, o uso de java.util.concurrent.atomic, estruturas de dados simultâneas, sincronização adequada e simultaneidade baseada em ator ajudará.
O melhor recurso para simultaneidade é o JCIP. Você também pode obter mais detalhes sobre a explicação acima aqui .
fonte
Há uma diferença técnica importante entre as condições da corrida e as corridas de dados. A maioria das respostas parece assumir que esses termos são equivalentes, mas não são.
Uma corrida de dados ocorre quando 2 instruções acessam o mesmo local de memória, pelo menos um desses acessos é uma gravação e não há ocorre antes da solicitação entre esses acessos. Agora, o que constitui um acontecimento antes do pedido está sujeito a muito debate, mas, em geral, os pares ulock-lock na mesma variável de bloqueio e os pares de sinal de espera na mesma variável de condição induzem uma ordem de antes do acontecimento.
Uma condição de corrida é um erro semântico. É uma falha que ocorre no tempo ou na ordem dos eventos que leva ao programa incorreto comportamento .
Muitas condições de corrida podem ser (e de fato são) causadas por corridas de dados, mas isso não é necessário. De fato, as corridas de dados e as condições de corrida não são necessárias nem suficientes para uma a outra. Esta postagem no blog também explica muito bem a diferença, com um exemplo simples de transação bancária. Aqui está outro exemplo simples que explica a diferença.
Agora que definimos a terminologia, vamos tentar responder à pergunta original.
Dado que as condições de corrida são erros semânticos, não existe uma maneira geral de detectá-las. Isso ocorre porque não há como ter um oráculo automatizado que possa distinguir o comportamento correto e incorreto do programa no caso geral. A detecção de corrida é um problema indecidível.
Por outro lado, as corridas de dados têm uma definição precisa que não se relaciona necessariamente à correção e, portanto, é possível detectá-las. Existem muitos tipos de detectores de corrida de dados (detecção de corrida de dados estática / dinâmica, detecção de corrida de dados com base em conjunto, detecção de corrida de dados baseada em fatos anteriores, detecção de corrida de dados híbrida). Um detector de corrida de dados dinâmicos de última geração é o ThreadSanitizer que funciona muito bem na prática.
O tratamento das corridas de dados em geral requer alguma disciplina de programação para induzir as ocorrências antes das arestas entre os acessos aos dados compartilhados (durante o desenvolvimento ou depois que eles são detectados usando as ferramentas mencionadas acima). isso pode ser feito por meio de bloqueios, variáveis de condição, semáforos, etc. No entanto, também é possível empregar diferentes paradigmas de programação, como passagem de mensagens (em vez de memória compartilhada), que evitam corridas de dados por construção.
fonte
Uma definição do tipo canônica é " quando dois threads acessam o mesmo local na memória ao mesmo tempo e pelo menos um dos acessos é uma gravação ". Na situação, o segmento "reader" pode obter o valor antigo ou o novo valor, dependendo de qual segmento "vence a corrida". Isso nem sempre é um bug - na verdade, alguns algoritmos de baixo nível realmente peludos fazem isso de propósito - mas geralmente deve ser evitado. @ Steve Gury dá um bom exemplo de quando isso pode ser um problema.
fonte
Uma condição de corrida é um tipo de bug, que acontece apenas com certas condições temporais.
Exemplo: Imagine que você tem dois threads, A e B.
No segmento A:
No segmento B:
Se o encadeamento A for antecipado logo após ter verificado que o objeto.a não é nulo, B funcionará
a = 0
e, quando o encadeamento A ganhar o processador, ele fará uma "divisão por zero".Esse bug só acontece quando o encadeamento A é antecipado logo após a instrução if, é muito raro, mas pode acontecer.
fonte
A condição de corrida não está relacionada apenas ao software, mas também ao hardware. Na verdade, o termo foi cunhado inicialmente pela indústria de hardware.
De acordo com a wikipedia :
A indústria de software adotou esse termo sem modificações, o que o torna um pouco difícil de entender.
Você precisa fazer alguma substituição para mapeá-lo para o mundo do software:
Portanto, a condição de corrida na indústria de software significa "dois threads" / "dois processos" correndo um contra o outro para "influenciar algum estado compartilhado", e o resultado final do estado compartilhado dependerá de alguma diferença sutil de tempo, que pode ser causada por algum problema específico. ordem de lançamento de encadeamento / processo, agendamento de encadeamento / processo etc.
fonte
Uma condição de corrida é uma situação na programação simultânea em que dois processos ou threads simultâneos competem por um recurso e o estado final resultante depende de quem obtém o recurso primeiro.
fonte
As condições de corrida ocorrem em aplicativos multithread ou sistemas multiprocessos. Uma condição de corrida, na sua forma mais básica, é qualquer coisa que pressupõe que duas coisas que não estão no mesmo segmento ou processo acontecerão em uma ordem específica, sem tomar medidas para garantir que elas aconteçam. Isso acontece normalmente quando dois threads passam mensagens configurando e verificando variáveis de membros de uma classe que ambos podem acessar. Quase sempre há uma condição de corrida quando um segmento chama suspensão para dar tempo a outro segmento para concluir uma tarefa (a menos que o sono esteja em loop, com algum mecanismo de verificação).
As ferramentas para prevenir condições de corrida dependem do idioma e do sistema operacional, mas algumas comuns são mutexes, seções críticas e sinais. Os mutexes são bons quando você quer ter certeza de que é o único a fazer alguma coisa. Os sinais são bons quando você quer ter certeza de que alguém terminou de fazer alguma coisa. Minimizar recursos compartilhados também pode ajudar a evitar comportamentos inesperados
Detectar as condições da corrida pode ser difícil, mas há alguns sinais. O código que depende muito da suspensão é propenso a condições de corrida, portanto, verifique primeiro se há chamadas para suspensão no código afetado. Adicionar adições particularmente longas também pode ser usado para depuração para tentar forçar uma ordem específica de eventos. Isso pode ser útil para reproduzir o comportamento, ver se é possível fazê-lo desaparecer alterando o tempo das coisas e para testar as soluções implementadas. Os dormentes devem ser removidos após a depuração.
O sinal de assinatura de que alguém tem uma condição de corrida é se houver um problema que ocorre apenas de forma intermitente em algumas máquinas. Erros comuns seriam falhas e impasses. Com o registro, você poderá encontrar a área afetada e voltar a trabalhar a partir daí.
fonte
A Microsoft publicou um artigo realmente detalhado sobre esse assunto de condições de corrida e impasses. O resumo mais resumido seria o parágrafo do título:
fonte
A situação em que o processo depende criticamente da sequência ou do tempo de outros eventos.
Por exemplo, o processador A e o processador B precisam de recursos idênticos para sua execução.
Existem ferramentas para detectar automaticamente a condição de corrida:
As condições da corrida podem ser tratadas por Mutex ou Semáforos . Eles agem como um bloqueio, permitindo que um processo adquira um recurso com base em certos requisitos para impedir a condição de corrida.
Existem várias maneiras de evitar a condição de corrida, como Evitar Seção Crítica .
fonte
Uma condição de corrida é uma situação indesejável que ocorre quando um dispositivo ou sistema tenta executar duas ou mais operações ao mesmo tempo, mas devido à natureza do dispositivo ou sistema, as operações devem ser executadas na seqüência adequada para serem feito corretamente.
Na memória ou armazenamento do computador, pode ocorrer uma condição de corrida se os comandos para ler e gravar uma grande quantidade de dados forem recebidos quase no mesmo instante, e a máquina tentar sobrescrever alguns ou todos os dados antigos enquanto esses dados antigos ainda estão sendo usados. ler. O resultado pode ser um ou mais dos seguintes procedimentos: falha no computador, "operação ilegal", notificação e desligamento do programa, erros na leitura dos dados antigos ou erros na gravação dos novos dados.
fonte
Aqui está o exemplo clássico de Saldo de conta bancária, que ajudará os novatos a entender os Threads em Java com facilidade nas condições de corrida:
fonte
Você pode impedir a condição de corrida , se usar classes "Atomic". O motivo é que o thread não separa a operação get and set, o exemplo está abaixo:
Como resultado, você terá 7 no link "ai". Embora você tenha feito duas ações, mas as duas operações confirmam o mesmo encadeamento e nenhum outro encadeamento interfere nisso, isso significa que não há condições de corrida!
fonte
Tente este exemplo básico para entender melhor as condições da corrida:
fonte
Você nem sempre quer descartar uma condição de corrida. Se você possui um sinalizador que pode ser lido e gravado por vários threads, e esse sinalizador é definido como 'concluído' por um thread, para que outro thread pare de processar quando o sinalizador está definido como 'concluído', você não deseja que " condição "a ser eliminada. De fato, este pode ser referido como uma condição de raça benigna.
No entanto, usando uma ferramenta para detecção de condição de corrida, ela será identificada como uma condição de corrida prejudicial.
Mais detalhes sobre as condições da corrida aqui, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx .
fonte
Considere uma operação que precise exibir a contagem assim que a contagem for incrementada. ou seja, assim que CounterThread incrementa o valor DisplayThread precisa exibir o valor atualizado recentemente.
Resultado
Aqui, CounterThread obtém o bloqueio com freqüência e atualiza o valor antes que DisplayThread o exiba. Aqui existe uma condição de corrida. A Condição de corrida pode ser resolvida usando o Synchronzation
fonte
Uma condição de corrida é uma situação indesejável que ocorre quando dois ou mais processos podem acessar e alterar os dados compartilhados ao mesmo tempo. Isso ocorreu porque havia acessos conflitantes a um recurso. O problema crítico da seção pode causar condições de corrida. Para resolver uma condição crítica do processo, realizamos apenas um processo por vez que executa a seção crítica.
fonte