Eu estava tendo uma discussão com um colega de equipe sobre o bloqueio no .NET. Ele é um cara realmente brilhante, com uma extensa experiência em programação de nível inferior e superior, mas sua experiência com programação de nível inferior excede em muito a minha. De qualquer forma, ele argumentou que o bloqueio do .NET deve ser evitado em sistemas críticos que devem estar sob carga pesada, se possível, a fim de evitar a possibilidade reconhecidamente pequena de um "encadeamento zumbi" travar um sistema. Eu costumo usar o bloqueio e não sabia o que era um "fio de zumbi", então perguntei. A impressão que tive da explicação dele é que um thread de zumbi foi encerrado, mas de alguma forma ainda mantém alguns recursos. Ele deu um exemplo de como um encadeamento de zumbis poderia quebrar um sistema, quando um encadeamento inicia algum procedimento após o bloqueio de algum objeto, e, em algum momento, é encerrado antes que o bloqueio possa ser liberado. Essa situação tem o potencial de travar o sistema, porque, eventualmente, as tentativas de executar esse método resultarão nos threads aguardando acesso a um objeto que nunca será retornado, porque o thread que está usando o objeto bloqueado está morto.
Eu acho que entendi tudo, mas se eu estiver fora da base, por favor me avise. O conceito fez sentido para mim. Eu não estava completamente convencido de que esse era um cenário real que poderia acontecer no .NET. Eu nunca ouvi falar de "zumbis", mas reconheço que os programadores que trabalharam em profundidade em níveis mais baixos tendem a ter uma compreensão mais profunda dos fundamentos da computação (como o threading). Definitivamente, vejo o valor do bloqueio, e já vi muitos programadores de classe mundial alavancarem o bloqueio. Também tenho capacidade limitada de avaliar isso por mim mesmo, porque sei que a lock(obj)
afirmação é realmente apenas um açúcar sintático para:
bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }
e porque Monitor.Enter
e Monitor.Exit
são marcados extern
. Parece concebível que o .NET faça algum tipo de processamento que proteja os threads da exposição a componentes do sistema que possam ter esse tipo de impacto, mas isso é puramente especulativo e provavelmente apenas com base no fato de que eu nunca ouvi falar de "threads de zumbis" antes. Então, espero obter algum feedback sobre isso aqui:
- Existe uma definição mais clara de um "fio de zumbi" do que eu expliquei aqui?
- Threads zumbis podem ocorrer no .NET? (Porque porque não?)
- Se aplicável, como forçar a criação de um thread zumbi no .NET?
- Se aplicável, como posso aproveitar o bloqueio sem arriscar um cenário de encadeamento de zumbis no .NET?
Atualizar
Eu fiz essa pergunta há pouco mais de dois anos. Hoje isso aconteceu:
fonte
wait
ouwaitpid
. O processo filho é então chamado de "processo zumbi". Veja também howtogeek.com/119815Respostas:
Parece uma boa explicação para mim - um thread que foi encerrado (e, portanto, não pode mais liberar nenhum recurso), mas cujos recursos (por exemplo, identificadores) ainda estão por aí e (potencialmente) causando problemas.
Eles certamente fazem, olha, eu fiz um!
Este programa inicia um thread
Target
que abre um arquivo e depois se mata imediatamente usandoExitThread
.O thread zumbi resultante nunca libera o identificador para o arquivo "test.txt" e, portanto, o arquivo permanece aberto até o término do programa (você pode verificar com o Process Explorer ou similar).O identificador para "test.txt" não será liberado até queGC.Collect
seja chamado - acontece que é ainda mais difícil do que eu pensava em criar um thread de zumbi que vazava identificadores)Não faça o que acabei de fazer!
Desde que o seu código seja limpo corretamente (use Safe Handles ou classes equivalentes se estiver trabalhando com recursos não gerenciados) e contanto que você não se esforce para eliminar threads de maneiras estranhas e maravilhosas (a maneira mais segura é apenas para nunca matar threads - deixe que eles terminem normalmente, ou através de exceções, se necessário), a única maneira de obter algo parecido com um thread de zumbi é se algo der muito errado (por exemplo, algo der errado no CLR).
De fato, é surpreendentemente difícil criar um encadeamento zumbi (eu tive que P / Invoke em uma função que essencialmente diz a você na documentação para não chamá-lo fora de C). Por exemplo, o seguinte código (terrível) na verdade não cria um thread de zumbi.
Apesar de cometer alguns erros terríveis, o identificador para "test.txt" ainda é fechado assim que
Abort
é chamado (como parte do finalizador para ofile
qual, sob as capas, usa SafeFileHandle para quebrar seu identificador de arquivo)O exemplo de bloqueio na resposta C.Evenhuis é provavelmente a maneira mais fácil de deixar de liberar um recurso (um bloqueio nesse caso) quando um encadeamento é encerrado de uma maneira não estranha, mas isso é facilmente corrigido usando uma
lock
instrução ou colocando o lançamento em umfinally
bloco.Veja também
lock
palavra - chave (mas apenas no .Net 3.5 e versões anteriores)fonte
ExitThread
ligação. Obviamente, funciona, mas parece mais um truque inteligente do que um cenário realista. Um dos meus objetivos é aprender o que não fazer para não criar acidentalmente threads de zumbi com código .NET. Eu provavelmente poderia ter descoberto que chamar o código C ++ que causa esse problema no código .NET produziria o efeito desejado. Você obviamente sabe muito sobre essas coisas. Você conhece outros casos (possivelmente estranhos, mas não estranhos o suficiente para nunca acontecer acidentalmente) com o mesmo resultado?Set excelInstance = GetObject(, "Excel.Application")
Limpei um pouco minha resposta, mas deixei a original abaixo para referência
É a primeira vez que ouvi falar do termo zumbis, então assumirei que sua definição é:
Um encadeamento que foi finalizado sem liberar todos os seus recursos
Portanto, dada essa definição, sim, você pode fazer isso no .NET, como em outras linguagens (C / C ++, java).
No entanto , não considero isso um bom motivo para não escrever código de missão crítica encadeado no .NET. Pode haver outros motivos para decidir contra o .NET, mas anular o .NET apenas porque você pode ter threads de zumbi de alguma forma não faz sentido para mim. Threads zumbis são possíveis em C / C ++ (eu até diria que é muito mais fácil mexer em C) e muitos aplicativos encadeados críticos estão em C / C ++ (comércio de alto volume, bancos de dados etc.).
Conclusão Se você está decidindo sobre um idioma a usar, sugiro que você considere o cenário geral: desempenho, habilidades de equipe, cronograma, integração com aplicativos existentes, etc. , mas como é muito difícil cometer esse erro no .NET em comparação com outros idiomas como C, acho que essa preocupação será ofuscada por outras coisas, como as mencionadas acima. Boa sorte!
Resposta original Zumbis † podem existir se você não escrever um código de encadeamento adequado. O mesmo vale para outras linguagens como C / C ++ e Java. Mas esse não é um motivo para não escrever código encadeado no .NET.
E, como em qualquer outro idioma, saiba o preço antes de usar algo. Também ajuda a saber o que está acontecendo sob o capô, para que você possa prever possíveis problemas.
Código confiável para sistemas de missão crítica não é fácil de escrever, seja qual for o idioma em que você esteja. Mas tenho certeza de que não é impossível fazer corretamente no .NET. Além disso, o AFAIK, .NET threading não é tão diferente do threading no C / C ++, ele usa (ou é construído a partir) das mesmas chamadas de sistema, exceto para algumas construções específicas do .net (como as versões leves de RWL e classes de eventos).
† primeira vez que ouvi falar do termo zumbis, mas com base na sua descrição, seu colega provavelmente quis dizer um thread que terminou sem liberar todos os recursos. Isso pode causar um conflito, vazamento de memória ou algum outro efeito colateral ruim. Obviamente, isso não é desejável, mas destacar o .NET por causa dessa possibilidade provavelmente não é uma boa ideia, pois também é possível em outros idiomas. Eu diria até que é mais fácil bagunçar em C / C ++ do que em .NET (especialmente em C onde você não possui RAII), mas muitos aplicativos críticos são escritos em C / C ++, certo? Então, isso realmente depende das suas circunstâncias individuais. Se você deseja extrair toda a velocidade do seu aplicativo e deseja chegar o mais próximo possível do bare metal, o .NET podenão ser a melhor solução. Se você tem um orçamento apertado e faz muita interface com serviços web / bibliotecas .net existentes / etc, o .NET pode ser uma boa escolha.
fonte
extern
métodos sem saída . Se você tem uma sugestão, eu adoraria ouvi-la. (2) Concordo que é possível fazer no .NET. Gostaria de acreditar que é possível com bloqueio, mas eu ainda tenho que encontrar uma resposta satisfatória para justificar que hojelocking
-lock
chave, provavelmente não, pois ela serializa a execução. Para maximizar a taxa de transferência, você precisará usar as construções corretas, dependendo das características do seu código. Eu diria que se você pode escrever código confiável em c / c ++ / java / qualquer que seja o uso de pthreads / boost / thread pools / o que for, então você pode escrevê-lo em C # também. Mas se você não pode escrever código confiável em qualquer idioma usando qualquer biblioteca, duvido que escrever em C # seja diferente.No momento, a maior parte da minha resposta foi corrigida pelos comentários abaixo. Não excluirei a resposta
porque preciso dos pontos de reputação,pois as informações nos comentários podem ser valiosas para os leitores.O Immortal Blue apontou que no .NET 2.0 e acima os
finally
blocos são imunes a interrupções de threads. E, como comentado por Andreas Niedermair, esse pode não ser um thread zumbi real, mas o exemplo a seguir mostra como o cancelamento de um thread pode causar problemas:No entanto, ao usar um
lock() { }
bloco,finally
ele ainda será executado quando aThreadAbortException
for disparado dessa maneira.As informações a seguir, como se vê, são válidas apenas para .NET 1 e .NET 1.1:
Se dentro do
lock() { }
bloco ocorrer outra exceção e elaThreadAbortException
chegar exatamente quando ofinally
bloco estiver prestes a ser executado, o bloqueio não será liberado. Como você mencionou, olock() { }
bloco é compilado como:Se outro encadeamento chamar
Thread.Abort()
dentro dofinally
bloco gerado , o bloqueio poderá não ser liberado.fonte
lock()
mas não consigo ver nenhum uso disso ... então - como isso está conectado? este é um uso "errado"Monitor.Enter
eMonitor.Exit
(sem o uso detry
efinally
)Monitor.Enter
eMonitor.Exit
sem o uso adequado de -try
efinally
, de qualquer maneira, seu cenário trava outros threads que podem se agarrar_lock
, então há um cenário de impasse - não necessariamente um thread de zumbi ... Além disso, você não está liberando o bloqueio emMain
... mas, hey ... talvez o OP está travando para supertrancamento vez de zumbi tópicos :)lock
ou adequadatry
/finally
-usageNão se trata de tópicos do Zombie, mas o livro Effective C # tem uma seção sobre a implementação do IDisposable, (item 17), que fala sobre objetos do Zombie que eu achei que você poderia achar interessantes.
Eu recomendo a leitura do livro, mas o essencial é que, se você tem uma classe implementando IDisposable ou contendo um Desctructor, a única coisa que você deve fazer é liberar recursos. Se você fizer outras coisas aqui, há uma chance de o objeto não ser coletado como lixo, mas também não estará acessível de forma alguma.
Ele fornece um exemplo semelhante ao abaixo:
Quando o destruidor desse objeto é chamado, uma referência a si mesma é colocada na lista global, o que significa que ele permanece vivo e na memória durante toda a vida útil do programa, mas não está acessível. Isso pode significar que os recursos (principalmente os recursos não gerenciados) podem não ser totalmente liberados, o que pode causar todo tipo de problema em potencial.
Um exemplo mais completo está abaixo. No momento em que o loop foreach é atingido, você tem 150 objetos na lista de Mortos-Vivos, cada um contendo uma imagem, mas a imagem foi gerada em GC e você recebe uma exceção se tentar usá-la. Neste exemplo, estou recebendo uma ArgumentException (Parameter não é válido) quando tento fazer alguma coisa com a imagem, tente salvá-la ou até mesmo exibir dimensões como altura e largura:
Novamente, eu sei que você estava perguntando sobre tópicos de zumbis em particular, mas o título da pergunta é sobre zumbis em .net, e eu me lembrei disso e pensei que outros poderiam achar interessante!
fonte
_undead
para ser estático?NullReferenceException
, porque sinto que a coisa que falta falta está mais ligada à máquina do que à aplicação. Isto está certo?IDisposable
esse o problema. Recebo a preocupação com os finalizadores (destruidores), mas simplesmenteIDisposable
não fazer com que um objeto vá para a fila do finalizador e arrisque esse cenário de zumbi. Este aviso diz respeito aos finalizadores e eles podem chamar métodos Dispose. Existem exemplos em queIDisposable
é usado em tipos sem finalizadores. O sentimento deve ser a limpeza de recursos, mas isso pode ser uma limpeza não trivial de recursos. O RX usa validamenteIDisposable
para limpar as assinaturas e pode chamar outros recursos a jusante. (Eu não downvote quer btw ...)Em sistemas críticos sob carga pesada, escrever código sem bloqueio é melhor principalmente por causa das melhorias no desempenho. Veja coisas como o LMAX e como ele aproveita a "simpatia mecânica" para grandes discussões sobre isso. Se preocupe com tópicos de zumbis? Eu acho que é um caso de ponta que é apenas um bug a ser resolvido, e não é uma razão boa o suficiente para não usar
lock
.Parece mais que seu amigo está apenas sendo chique e exibindo seu conhecimento de terminologia exótica obscura para mim! Durante todo o tempo em que eu estava executando os laboratórios de desempenho na Microsoft UK, nunca me deparei com uma instância desse problema no .NET.
fonte
lock
afirmação!lock
blocos grandes e grainizados (relativamente) de fácil compreensão são muito bons. Eu evitaria introduzir a complexidade por uma questão de otimização de desempenho até que você saiba que precisa.Concordo que "Zombie Threads" existe, é um termo para se referir ao que acontece com Threads que são deixados com recursos que eles não soltam e ainda não morrem completamente, daí o nome "zombie". A explicação dessa referência está certa no dinheiro!
Sim, eles podem ocorrer. É uma referência e, na verdade, referida pelo Windows como "zumbi": o MSDN usa a palavra "zumbi" para processos / threads inoperantes
Acontecer com frequência é outra história, e depende de suas técnicas e práticas de codificação, pois para você que gosta de Thread Locking e faz isso há algum tempo, eu nem me preocupo com esse cenário.
E sim, como o @KevinPanko mencionou corretamente nos comentários, "Zombie Threads" vem do Unix, e é por isso que eles são usados no XCode-ObjectiveC e chamados de "NSZombie" e usados para depuração. Ele se comporta da mesma maneira ... a única diferença é que um objeto que deveria ter morrido se torna um "ZombieObject" para depuração, em vez do "Zombie Thread", que pode ser um problema em potencial no seu código.
fonte
Eu posso fazer threads de zumbi com bastante facilidade.
Isso vaza as alças de thread (para
Join()
). É apenas mais um vazamento de memória no que diz respeito ao mundo gerenciado.Agora, matar um fio de uma maneira que realmente trava é uma dor na parte traseira, mas possível. O outro cara
ExitThread()
faz o trabalho. Como ele descobriu, o identificador de arquivo foi limpo pelo GC, mas umlock
objeto em volta não. Mas por que você faria isso?fonte