Recentemente, tenho trabalhado em projetos que usam fortemente o encadeamento. Eu acho que estou bem em projetá-los; use o design sem estado, tanto quanto possível, bloqueie o acesso a todos os recursos que mais de um encadeamento precisa, etc. Minha experiência em programação funcional ajudou imensamente.
No entanto, ao ler o código de thread de outras pessoas, fico confuso. Estou depurando um impasse no momento e, como o estilo e o design de codificação são diferentes do meu estilo pessoal, estou tendo dificuldades para ver possíveis condições de impasse.
O que você procura ao depurar deadlocks?
debugging
multithreading
Michael K
fonte
fonte
Respostas:
Se a situação for um conflito real (ou seja, dois encadeamentos mantêm dois bloqueios diferentes, mas pelo menos um encadeamento deseja um bloqueio que o outro encadea), será necessário abandonar todas as pré-concepções de como os encadeamentos ordenam o bloqueio. Supor nada. Você pode remover todos os comentários do código que está visualizando, pois esses comentários podem fazer com que você acredite em algo que não é verdadeiro. É difícil enfatizar isso o suficiente: não assuma nada.
Depois disso, determine quais bloqueios serão retidos enquanto um encadeamento tenta bloquear outra coisa. Se puder, assegure-se de que uma linha seja desbloqueada na ordem inversa do bloqueio. Ainda melhor, assegure-se de que uma linha segure apenas uma trava por vez.
Trabalhe meticulosamente na execução de um thread e examine todos os eventos de bloqueio. Em cada bloqueio, determine se um encadeamento retém outros bloqueios e, em caso afirmativo, em que circunstâncias outro encadeamento, executando um caminho de execução semelhante, pode chegar ao evento de bloqueio em consideração.
Certamente é possível que você não encontre o problema antes de ficar sem tempo ou dinheiro.
fonte
Como outros já disseram ... se você puder obter informações úteis para o log, tente primeiro, pois é a coisa mais fácil de fazer.
Identifique os bloqueios envolvidos. Mude todos os mutex / semáforos que esperam eternamente para esperas cronometradas ... algo ridiculamente longo, como 5 minutos. Registre o erro quando atingir o tempo limite. Isso pelo menos indicará você na direção de um dos bloqueios envolvidos no problema. Dependendo da variabilidade do tempo, você pode ter sorte e encontrar os dois bloqueios após algumas execuções. Use os códigos / condições de falha de função para registrar um rastreamento de pseudo-pilha após a espera temporizada falhar para identificar como você chegou lá em primeiro lugar. Isso deve ajudá-lo a identificar o segmento envolvido no problema.
Outra coisa que você pode tentar é criar uma biblioteca de wrapper em torno de seus serviços de mutex / semáforo. Acompanhe quais threads têm cada mutex e quais threads estão esperando no mutex. Crie um encadeamento de monitor que verifique por quanto tempo os encadeamentos estão bloqueando. Acione com duração razoável e despeje as informações de estado que você está rastreando.
Em algum momento, será necessária uma inspeção simples e antiga do código.
fonte
O primeiro passo (como Péter diz) é o log. Embora, na minha experiência, isso seja frequentemente problemático. No processamento paralelo pesado, isso geralmente não é possível. Eu tive que depurar algo semelhante com uma rede neural uma vez, que processou 100k de nós por segundo. O erro ocorreu apenas depois de várias horas e até uma única linha de saída diminuiu tanto as coisas que levaria dias. Se o registro for possível, concentre-se menos nos dados, mas mais no fluxo do programa, até que você saiba em qual parte isso acontece. Apenas uma linha simples no início de cada função e, se você encontrar a função correta, divida-a em pedaços menores.
Outra opção é remover partes do código e dados para localizar o bug. Talvez até escreva um pequeno programa que leve apenas algumas das classes e execute apenas os testes mais básicos (ainda em vários segmentos, é claro). Remova tudo que estiver relacionado à GUI, por exemplo, qualquer saída sobre o estado de processamento real. (Eu achei a interface do usuário a fonte do bug com bastante frequência)
No seu código, tente seguir o fluxo lógico completo de controle entre inicializar o bloqueio e liberá-lo. Um erro comum pode ser bloquear no início de uma função, desbloquear no final, mas ter uma declaração de retorno condicional em algum lugar no meio. Exceções também podem impedir a liberação.
fonte
Meus melhores amigos foram declarações de impressão / log em lugares interessantes do código. Isso geralmente me ajuda a entender melhor o que realmente está acontecendo dentro do aplicativo, sem interromper o tempo entre diferentes threads, o que poderia impedir a reprodução do bug.
Se isso falhar, meu único método restante é observar o código e tentar criar um modelo mental dos vários threads e interações, e tentar pensar em possíveis maneiras malucas de alcançar o que aparentemente aconteceu :-) Mas eu não considero-me um matador de impasse muito experiente. Espero que outros possam dar melhores idéias, das quais também posso aprender :-)
fonte
Primeiro de tudo, tente obter o autor desse código. Ele provavelmente terá a ideia do que escreveu. mesmo que vocês dois não consigam identificar o problema apenas conversando, pelo menos você pode sentar com ele para identificar a parte do impasse, que será muito mais rápida do que você entende o código sem ajuda.
Caso contrário, como Péter Török disse, o registro é provavelmente o caminho. Até onde eu sei, o Debugger fez um trabalho ruim no ambiente com vários threads. tente localizar onde está a trava, obtenha um conjunto dos recursos que estão aguardando e em que condição ocorre a condição de corrida.
fonte
Esta pergunta me atrai;) Antes de tudo, considere-se com sorte, pois você conseguiu reproduzir o problema de forma consistente a cada execução. Se você receber a mesma exceção com o mesmo rastreamento de pilha de cada vez, deverá ser bastante direto. Caso contrário, não confie tanto no stacktrace, apenas monitore o acesso aos objetos globais e seu estado muda durante a execução.
fonte
Se você precisar depurar impasses, já está com problemas. Como regra, use bloqueios pelo menor tempo possível - ou, se não for possível, de jeito nenhum. Qualquer situação em que você trava e depois passa para um código não trivial deve ser evitada.
É claro que depende do seu ambiente de programação, mas você deve considerar coisas como filas seqüenciais que podem permitir que você acesse um recurso apenas de um único encadeamento.
E há uma estratégia antiga, mas infalível: atribua um "nível" a cada bloqueio, começando no nível 0. Se você usar um bloqueio de nível 0, não será permitido nenhum outro bloqueio. Depois de pegar um bloqueio de nível 1, você pode pegar um bloqueio de nível 0. Depois de pegar um bloqueio de nível 10, você pode travar nos níveis 9 ou inferior, etc.
Se você acha isso impossível, é necessário corrigir o código, pois você encontrará conflitos.
fonte