J'avais une discussion avec un coéquipier sur le verrouillage en .NET. C'est un gars très brillant avec une vaste expérience dans la programmation de niveau inférieur et supérieur, mais son expérience avec la programmation de niveau inférieur dépasse de loin la mienne. Quoi qu'il en soit, il a fait valoir que le verrouillage .NET devrait être évité sur les systèmes critiques censés être soumis à une charge élevée si possible afin d'éviter la possibilité certes faible d'un «fil zombie» de planter un système. J'utilise régulièrement le verrouillage et je ne savais pas ce qu'était un «fil zombie», alors j'ai demandé. L'impression que j'ai eue de son explication est qu'un fil zombie est un fil qui s'est terminé mais qui conserve en quelque sorte encore certaines ressources. Un exemple qu'il a donné de la façon dont un fil zombie pourrait briser un système si un fil commence une procédure après avoir verrouillé un objet, puis se termine à un moment donné avant que le verrou puisse être libéré. Cette situation a le potentiel de planter le système, car en fin de compte, les tentatives d'exécution de cette méthode entraîneront tous les threads en attente d'accès à un objet qui ne sera jamais renvoyé, car le thread qui utilise l'objet verrouillé est mort.
Je pense que j'ai compris l'essentiel, mais si je suis hors de la base, faites-le moi savoir. Le concept avait du sens pour moi. Je n'étais pas complètement convaincu qu'il s'agissait d'un scénario réel qui pourrait se produire dans .NET. Je n'ai jamais entendu parler de "zombies", mais je reconnais que les programmeurs qui ont travaillé en profondeur à des niveaux inférieurs ont tendance à avoir une compréhension plus approfondie des principes fondamentaux de l'informatique (comme le filetage). Cependant, je vois vraiment la valeur du verrouillage, et j'ai vu de nombreux programmeurs de classe mondiale tirer parti du verrouillage. J'ai également une capacité limitée à évaluer cela par moi-même parce que je sais que l' lock(obj)
énoncé est vraiment juste du sucre syntaxique pour:
bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }
et parce que Monitor.Enter
et Monitor.Exit
sont marqués extern
. Il semble concevable que .NET fasse une sorte de traitement qui protège les threads de l'exposition aux composants du système qui pourraient avoir ce genre d'impact, mais c'est purement spéculatif et probablement juste basé sur le fait que je n'ai jamais entendu parler de "threads zombies" avant. J'espère donc pouvoir obtenir des commentaires à ce sujet ici:
- Existe-t-il une définition plus claire d'un "fil zombie" que ce que j'ai expliqué ici?
- Les threads zombies peuvent-ils se produire sur .NET? (Pourquoi pourquoi pas?)
- Le cas échéant, comment pourrais-je forcer la création d'un fil zombie dans .NET?
- Le cas échéant, comment puis-je tirer parti du verrouillage sans risquer un scénario de thread zombie dans .NET?
Mise à jour
J'ai posé cette question il y a un peu plus de deux ans. Aujourd'hui, c'est arrivé:
la source
wait
ouwaitpid
. Le processus enfant est alors appelé "processus zombie". Voir aussi howtogeek.com/119815Réponses:
Cela me semble être une assez bonne explication - un thread qui s'est terminé (et ne peut donc plus libérer de ressources), mais dont les ressources (par exemple les poignées) sont toujours présentes et (potentiellement) causent des problèmes.
Ils le font, regardez, j'en ai fait un!
Ce programme démarre un thread
Target
qui ouvre un fichier, puis se tue immédiatement en utilisantExitThread
.Le thread zombie résultant ne relâchera jamais la poignée vers le fichier "test.txt" et donc le fichier restera ouvert jusqu'à la fin du programme (vous pouvez vérifier avec l'explorateur de processus ou similaire).Le handle de "test.txt" ne sera pas libéré avant l'GC.Collect
appel - il s'avère que c'est encore plus difficile que je ne le pensais de créer un thread zombie qui fuit les poignées)Ne fais pas ce que je viens de faire!
Tant que votre code se nettoie correctement après lui-même (utilisez des poignées sûres ou des classes équivalentes si vous travaillez avec des ressources non gérées), et tant que vous ne faites pas de votre mieux pour tuer les threads de manière étrange et merveilleuse (la manière la plus sûre est simplement de ne jamais tuer des fils - les laisser se terminent normalement, ou par des exceptions si nécessaire), la seule façon que vous allez avoir quelque chose qui ressemble à un fil de zombie est si quelque chose est allé très mal (par exemple , quelque chose va mal dans le CLR).
En fait, il est en fait étonnamment difficile de créer un fil zombie (j'ai dû P / Invoke dans une fonction qui vous dit essentiellement dans la documentation de ne pas l'appeler en dehors de C). Par exemple, le code (horrible) suivant ne crée en fait pas de fil zombie.
Malgré quelques erreurs assez horribles, le handle de "test.txt" est toujours fermé dès qu'il
Abort
est appelé (dans le cadre du finaliseur pourfile
lequel, sous les couvertures, utilise SafeFileHandle pour envelopper son handle de fichier)L'exemple de verrouillage dans la réponse de C.Evenhuis est probablement le moyen le plus simple de ne pas libérer une ressource (un verrou dans ce cas) lorsqu'un thread se termine de manière non bizarre, mais cela peut être facilement résolu en utilisant une
lock
instruction à la place, ou mettre la libération dans unfinally
bloc.Voir également
lock
mot clé (mais uniquement dans .Net 3.5 et versions antérieures)la source
ExitThread
appel. Évidemment, cela fonctionne, mais cela ressemble plus à une astuce intelligente qu'à un scénario réaliste. Un de mes objectifs est d'apprendre ce qu'il ne faut pas faire pour ne pas créer accidentellement des threads zombies avec du code .NET. J'aurais probablement pu comprendre que l'appel de code C ++ connu pour causer ce problème à partir du code .NET produirait l'effet souhaité. Vous en savez évidemment beaucoup sur ce genre de choses. Connaissez-vous d'autres cas (peut-être étranges, mais pas assez étranges pour ne jamais arriver involontairement) avec le même résultat?Set excelInstance = GetObject(, "Excel.Application")
J'ai un peu nettoyé ma réponse, mais j'ai laissé l'original ci-dessous pour référence
C'est la première fois que j'entends parler du terme zombies, donc je suppose que sa définition est:
Un thread qui s'est terminé sans libérer toutes ses ressources
Donc, étant donné cette définition, alors oui, vous pouvez le faire dans .NET, comme avec d'autres langages (C / C ++, java).
Cependant , je ne pense pas que ce soit une bonne raison de ne pas écrire de code critique et fileté dans .NET. Il peut y avoir d'autres raisons de décider contre .NET, mais annuler .NET simplement parce que vous pouvez avoir des fils zombies n'a pas de sens pour moi. Les threads zombies sont possibles en C / C ++ (je dirais même qu'il est beaucoup plus facile de gâcher en C) et beaucoup d'applications critiques et filetées sont en C / C ++ (trading à volume élevé, bases de données, etc.).
Conclusion Si vous êtes en train de décider d'une langue à utiliser, je vous suggère de prendre en considération la situation dans son ensemble: performances, compétences d'équipe, calendrier, intégration avec les applications existantes, etc. Bien sûr, les fils de zombies sont quelque chose que vous devriez penser , mais comme il est si difficile de faire cette erreur dans .NET par rapport à d'autres langages comme C, je pense que cette préoccupation sera éclipsée par d'autres choses comme celles mentionnées ci-dessus. Bonne chance!
La réponse originale Zombies † peut exister si vous n'écrivez pas le bon code de thread. La même chose est vraie pour d'autres langages comme C / C ++ et Java. Mais ce n'est pas une raison pour ne pas écrire de code threadé dans .NET.
Et comme pour toute autre langue, sachez le prix avant d'utiliser quelque chose. Il permet également de savoir ce qui se passe sous le capot afin de prévoir tout problème potentiel.
Le code fiable pour les systèmes critiques n'est pas facile à écrire, quelle que soit la langue dans laquelle vous vous trouvez. Mais je suis certain qu'il n'est pas impossible de le faire correctement en .NET. De plus, AFAIK, le threading .NET n'est pas si différent du threading en C / C ++, il utilise (ou est construit à partir de) les mêmes appels système, à l'exception de certaines constructions spécifiques à .net (comme les versions légères de RWL et les classes d'événements).
† La première fois que j'ai entendu parler du terme zombies, mais d'après votre description, votre collègue voulait probablement dire un fil qui s'est terminé sans libérer toutes les ressources. Cela pourrait potentiellement provoquer un blocage, une fuite de mémoire ou d'autres effets secondaires néfastes. Ce n'est évidemment pas souhaitable, mais isoler .NET à cause de cette possibilité n'est probablement pas une bonne idée car c'est également possible dans d'autres langues. Je dirais même qu'il est plus facile de gâcher en C / C ++ qu'en .NET (surtout en C où vous n'avez pas RAII) mais beaucoup d'applications critiques sont écrites en C / C ++, n'est-ce pas? Cela dépend donc vraiment de votre situation personnelle. Si vous souhaitez extraire chaque once de vitesse de votre application et vous rapprocher le plus possible du bare metal, alors .NET peutne pas être la meilleure solution. Si vous avez un budget serré et que vous faites beaucoup d'interfaçage avec les services Web / les bibliothèques .net existantes / etc., alors .NET peut être un bon choix.
la source
extern
méthodes sans issue . Si vous avez une suggestion, j'aimerais l'entendre. (2) Je conviens qu'il est possible de le faire en .NET. J'aimerais croire que c'est possible avec le verrouillage, mais je n'ai pas encore trouvé de réponse satisfaisante pour le justifier aujourd'huilocking
-lock
clé, alors probablement pas car il sérialise l'exécution. Pour maximiser le débit, vous devez utiliser les bonnes constructions en fonction des caractéristiques de votre code. Je dirais que si vous pouvez écrire du code fiable en c / c ++ / java / quoi que ce soit en utilisant pthreads / boost / thread pools / quoi que ce soit, alors vous pouvez aussi l'écrire en C #. Mais si vous ne pouvez pas écrire de code fiable dans n'importe quel langage à l'aide d'une bibliothèque, je doute que l'écriture en C # soit différente.À l'heure actuelle, la plupart de ma réponse a été corrigée par les commentaires ci-dessous. Je ne supprimerai pas la réponse
car j'ai besoin des points de réputationcar les informations contenues dans les commentaires peuvent être utiles aux lecteurs.Immortal Blue a souligné que dans .NET 2.0 et les
finally
blocs supérieurs sont immunisés contre les abandons de threads. Et comme l'a commenté Andreas Niedermair, il ne s'agit peut-être pas d'un véritable thread zombie, mais l'exemple suivant montre comment l'interruption d'un thread peut provoquer des problèmes:Cependant, lors de l'utilisation d'un
lock() { }
bloc, lefinally
serait toujours exécuté quand unThreadAbortException
est tiré de cette façon.Il s'avère que les informations suivantes ne sont valables que pour .NET 1 et .NET 1.1:
Si à l'intérieur du
lock() { }
bloc, une autre exception se produit etThreadAbortException
arrive exactement au moment où lefinally
bloc est sur le point d'être exécuté, le verrou n'est pas libéré. Comme vous l'avez mentionné, lelock() { }
bloc est compilé comme suit:Si un autre thread appelle
Thread.Abort()
à l'intérieur dufinally
bloc généré , le verrou peut ne pas être libéré.la source
lock()
mais je ne vois aucune utilisation de cela ... alors - comment est-ce connecté? il s'agit d'une utilisation "incorrecte" deMonitor.Enter
etMonitor.Exit
(sans l'utilisation detry
etfinally
)Monitor.Enter
etMonitor.Exit
sans une utilisation appropriée detry
etfinally
- de toute façon, votre scénario verrouillera d'autres threads qui pourraient s'accrocher_lock
, donc il y a un scénario de blocage - pas nécessairement un fil zombie ... De plus, vous ne relâchez pas le verrouMain
... mais, hé ... peut-être que l'OP se verrouille pour un blocage au lieu de fils zombies :)lock
ou la bonnetry
/finally
-usageIl ne s'agit pas de threads Zombie, mais le livre Effective C # a une section sur l'implémentation d'IDisposable, (article 17), qui parle des objets Zombie que je pensais que vous pourriez trouver intéressants.
Je recommande de lire le livre lui-même, mais l'essentiel est que si vous avez une classe implémentant IDisposable ou contenant un Desctructor, la seule chose que vous devriez faire dans l'un ou l'autre est de libérer des ressources. Si vous faites d'autres choses ici, il est possible que l'objet ne soit pas récupéré, mais qu'il ne soit pas accessible de quelque manière que ce soit.
Il donne un exemple similaire à ci-dessous:
Lorsque le destructeur sur cet objet est appelé, une référence à lui-même est placée sur la liste globale, ce qui signifie qu'il reste en vie et en mémoire pendant la durée de vie du programme, mais n'est pas accessible. Cela peut signifier que les ressources (en particulier les ressources non gérées) peuvent ne pas être entièrement libérées, ce qui peut provoquer toutes sortes de problèmes potentiels.
Un exemple plus complet est ci-dessous. Au moment où la boucle foreach est atteinte, vous avez 150 objets dans la liste des morts-vivants contenant chacun une image, mais l'image a été GC'd et vous obtenez une exception si vous essayez de l'utiliser. Dans cet exemple, j'obtiens une ArgumentException (le paramètre n'est pas valide) lorsque j'essaie de faire quoi que ce soit avec l'image, que j'essaie de l'enregistrer ou même d'afficher des dimensions telles que la hauteur et la largeur:
Encore une fois, je sais que vous posiez des questions sur les fils de zombies en particulier, mais le titre de la question concerne les zombies dans .net, et cela m'a rappelé et j'ai pensé que d'autres pourraient le trouver intéressant!
la source
_undead
censé être statique?NullReferenceException
, car j'ai l'impression que la chose manquante doit être plus liée à la machine qu'à l'application. Est-ce correct?IDisposable
le problème. Je m'inquiète des finaliseurs (destructeurs), mais le simple fait deIDisposable
ne pas faire en sorte qu'un objet entre dans la file d'attente du finaliseur et risque ce scénario zombie. Cet avertissement concerne les finaliseurs et ils pourraient appeler des méthodes Dispose. Il existe des exemples oùIDisposable
est utilisé dans les types sans finaliseurs. Le sentiment devrait être pour le nettoyage des ressources, mais cela pourrait être un nettoyage des ressources non trivial. RX utilise valablementIDisposable
pour nettoyer les abonnements et peut appeler d'autres ressources en aval. (Je n'ai pas non plus downvote soit btw ...)Sur les systèmes critiques sous forte charge, l'écriture de code sans verrouillage est préférable, principalement en raison des améliorations des performances. Regardez des trucs comme LMAX et comment il exploite la "sympathie mécanique" pour de grandes discussions à ce sujet. Mais vous vous inquiétez des fils de zombies? Je pense que c'est un cas de bord qui est juste un bug à résoudre, et ce n'est pas une raison suffisante pour ne pas l'utiliser
lock
.On dirait plus que votre ami est juste fantaisiste et affiche sa connaissance de la terminologie exotique obscure pour moi! Pendant tout le temps où je dirigeais les laboratoires de performances de Microsoft UK, je n'ai jamais rencontré d'instance de ce problème dans .NET.
la source
lock
déclaration!lock
blocs gras (relativement) faciles à comprendre sont très bien. Je voudrais éviter d'introduire de la complexité pour l'optimisation des performances jusqu'à ce que vous sachiez que vous en avez besoin.Je suis d'accord sur le fait que "Threads Zombie" existe, c'est un terme pour désigner ce qui se passe avec les Threads qui se retrouvent avec des ressources qu'ils ne lâchent pas et qui ne meurent pas complètement, d'où le nom "zombie", donc votre l'explication de cette référence est assez juste sur l'argent!
Oui, ils peuvent se produire. C'est une référence, et en fait référencé par Windows comme "zombie": MSDN utilise le mot "Zombie" pour les processus / threads morts
Se produire fréquemment, c'est une autre histoire, et dépend de vos techniques et pratiques de codage, comme pour vous qui aimez Thread Locking et qui l'ont fait depuis un moment, je ne m'inquiéterais même pas de ce scénario qui vous arrive.
Et oui, comme @KevinPanko l'a correctement mentionné dans les commentaires, "Zombie Threads" vient d'Unix, c'est pourquoi ils sont utilisés dans XCode-ObjectiveC et appelés "NSZombie" et utilisés pour le débogage. Il se comporte à peu près de la même manière ... la seule différence est qu'un objet qui aurait dû mourir devient un "ZombieObject" pour le débogage au lieu du "Zombie Thread" qui pourrait être un problème potentiel dans votre code.
la source
Je peux faire des fils zombies assez facilement.
Cela fuit les poignées de fil (pour
Join()
). C'est juste une autre fuite de mémoire en ce qui nous concerne dans le monde géré.Maintenant, tuer un fil de manière à ce qu'il retienne réellement les verrous est une douleur à l'arrière mais possible. L'autre gars
ExitThread()
fait le boulot. Comme il l'a découvert, le descripteur de fichier a été nettoyé par le GC, mais paslock
autour d'un objet. Mais pourquoi voudriez-vous faire ça?la source