Les zombies existent-ils… dans .NET?

385

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.Enteret Monitor.Exitsont 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:

  1. Existe-t-il une définition plus claire d'un "fil zombie" que ce que j'ai expliqué ici?
  2. Les threads zombies peuvent-ils se produire sur .NET? (Pourquoi pourquoi pas?)
  3. Le cas échéant, comment pourrais-je forcer la création d'un fil zombie dans .NET?
  4. 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é:

L'objet est dans un état zombie.

smartcaveman
la source
8
êtes-vous sûr que votre partenaire ne parle pas de blocage?
Andreas Niedermair
10
@AndreasNiedermair - Je sais ce qu'est l'impasse et ce n'était clairement pas une question d'abus de cette terminologie. L'impasse a été mentionnée dans la conversation et était clairement distincte d'un "fil zombie". Pour moi, la principale distinction est qu'un verrou mort a une dépendance non résoluble bidirectionnelle alors que le thread zombie est unidirectionnel et nécessite un processus terminé. Si vous n'êtes pas d'accord et pensez qu'il existe une meilleure façon de voir ces choses, veuillez expliquer
smartcaveman
19
Je pense que le terme "zombie" vient en fait d'un backgound UNIX, comme dans "processus zombie", non ??? Il existe une définition claire d'un "processus zombie" sous UNIX: il décrit un processus enfant qui s'est terminé mais où le parent du processus enfant doit encore "libérer" le processus enfant (et ses ressources) en appelant waitou waitpid. Le processus enfant est alors appelé "processus zombie". Voir aussi howtogeek.com/119815
hogliux
9
Si une partie de votre programme se bloque, laissant le programme dans un état indéfini, cela peut bien sûr causer des problèmes avec le reste de votre programme. La même chose peut se produire si vous gérez de manière incorrecte les exceptions dans un programme à thread unique. Le problème n'est pas avec les threads, le problème est que vous avez un état mutable global et que vous ne gérez pas correctement la terminaison de thread inattendue. Votre collègue "vraiment brillant" est totalement hors de propos de celui-ci.
Jim Mischel
9
"Depuis les premiers ordinateurs, il y a toujours eu des fantômes dans la machine. Des segments aléatoires de code qui se sont regroupés pour former des protocoles inattendus ..."
Chris Laplante

Réponses:

242
  • Existe-t-il une définition plus claire d'un "fil zombie" que ce que j'ai expliqué ici?

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.

  • 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?

Ils le font, regardez, j'en ai fait un!

[DllImport("kernel32.dll")]
private static extern void ExitThread(uint dwExitCode);

static void Main(string[] args)
{
    new Thread(Target).Start();
    Console.ReadLine();
}

private static void Target()
{
    using (var file = File.Open("test.txt", FileMode.OpenOrCreate))
    {
        ExitThread(0);
    }
}

Ce programme démarre un thread Targetqui ouvre un fichier, puis se tue immédiatement en utilisant ExitThread. 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.Collectappel - 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)

  • Le cas échéant, comment puis-je tirer parti du verrouillage sans risquer un scénario de thread zombie dans .NET?

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.

static void Main(string[] args)
{
    var thread = new Thread(Target);
    thread.Start();
    // Ugh, never call Abort...
    thread.Abort();
    Console.ReadLine();
}

private static void Target()
{
    // Ouch, open file which isn't closed...
    var file = File.Open("test.txt", FileMode.OpenOrCreate);
    while (true)
    {
        Thread.Sleep(1);
    }
    GC.KeepAlive(file);
}

Malgré quelques erreurs assez horribles, le handle de "test.txt" est toujours fermé dès qu'il Abortest appelé (dans le cadre du finaliseur pour filelequel, 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 lockinstruction à la place, ou mettre la libération dans un finallybloc.

Voir également

Justin
la source
3
Je me souviens quand j'ai joué avec des trucs à sauvegarder dans Excel en utilisant un backgroundworker je n'ai pas libéré toutes les ressources tout le temps (parce que j'ai juste sauté le débogage, etc.). dans le gestionnaire de tâches, j'ai vu ensuite environ 50 processus Excel. ai-je créé des processus zombieexcel?
Stefan
3
@Justin - +1 - Excellente réponse. Je suis cependant un peu sceptique quant à votre ExitThreadappel. É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?
smartcaveman
3
Donc, la réponse est «pas en c # 4», non? Si vous devez sauter du CLR pour obtenir un fil zombie, cela ne semble pas être un problème .Net.
Gusdor
4
@Stefan, je dirais certainement pas. J'ai fait ce que vous avez décrit à maintes reprises. Les processus Excel ne sont pas des zombies car ils sont toujours en cours d'exécution mais ne sont pas immédiatement accessibles via l'interface utilisateur normale. Vous devriez pouvoir les récupérer via des appels à GetObject . Set excelInstance = GetObject(, "Excel.Application")
Daniel
7
"Le thread zombie résultant ne relâchera jamais la poignée dans le fichier" test.txt "et donc le fichier restera ouvert jusqu'à la fin du programme" est incorrect. Petite preuve: `static void Main (string [] args) {new Thread (GcCollect) .Start (); nouveau Thread (Target) .Start (); Console.ReadLine (); } Private static void Target () {using (var file = File.Open ("test.txt", FileMode.OpenOrCreate)) {ExitThread (0); }} private static void GcCollect () {while (true) {Thread.Sleep (10000); GC.Collect (); }} `
Sinix
46

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.

Jerahmeel
la source
(1) Je ne sais pas comment comprendre ce qui se passe sous le capot lorsque je frappe des externmé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'hui
smartcaveman
1
@smartcaveman si vous entendez par mot locking- lockclé, 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.
Jerahmeel
@smartcaveman quant à savoir ce qui se cache sous le capot, google aide les tas et si votre problème est trop exotique pour être trouvé sur le web, le réflecteur est très pratique. Mais pour les classes de threading, je trouve la documentation MSDN très utile. Et beaucoup d'entre eux ne sont que des wrappers pour les mêmes appels système que vous utilisez dans C anway.
Jerahmeel
1
@smartcaveman pas du tout, ce n'est pas ce que je voulais dire. Je suis désolé si cela est tombé de cette façon. Ce que je voulais dire, c'est que vous ne devriez pas être trop rapide pour supprimer .NET lors de l'écriture d'une application critique. Bien sûr, vous pouvez faire des choses qui sont peut-être bien pires que les threads zombies (qui, je pense, ne sont que des threads qui n'ont pas libéré de ressources non gérées, ce qui peut totalement se produire dans d'autres langues: stackoverflow.com/questions/14268080/… ), mais encore une fois, cela ne signifie pas que .NET n'est pas une solution viable.
Jerahmeel
2
Je ne pense pas que .NET soit une mauvaise technologie du tout. C'est en fait mon principal cadre de développement. Mais, à cause de cela, je pense qu'il est important que je comprenne les défauts auxquels il est sensible. Fondamentalement, je singularise .NET, mais parce que j'aime ça, pas parce que je n'aime pas. (Et pas de soucis bro, je vous ai édité en 1)
smartcaveman
26

À 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éputation car les informations contenues dans les commentaires peuvent être utiles aux lecteurs.

Immortal Blue a souligné que dans .NET 2.0 et les finallyblocs 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:

class Program
{
    static readonly object _lock = new object();

    static void Main(string[] args)
    {
        Thread thread = new Thread(new ThreadStart(Zombie));
        thread.Start();
        Thread.Sleep(500);
        thread.Abort();

        Monitor.Enter(_lock);
        Console.WriteLine("Main entered");
        Console.ReadKey();
    }

    static void Zombie()
    {
        Monitor.Enter(_lock);
        Console.WriteLine("Zombie entered");
        Thread.Sleep(1000);
        Monitor.Exit(_lock);
        Console.WriteLine("Zombie exited");
    }
}

Cependant, lors de l'utilisation d'un lock() { }bloc, le finallyserait toujours exécuté quand un ThreadAbortExceptionest 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 et ThreadAbortExceptionarrive exactement au moment où le finallybloc est sur le point d'être exécuté, le verrou n'est pas libéré. Comme vous l'avez mentionné, le lock() { }bloc est compilé comme suit:

finally 
{
    if (lockWasTaken) 
        Monitor.Exit(temp); 
}

Si un autre thread appelle Thread.Abort()à l'intérieur du finallybloc généré , le verrou peut ne pas être libéré.

C.Evenhuis
la source
2
vous parlez lock()mais je ne vois aucune utilisation de cela ... alors - comment est-ce connecté? il s'agit d'une utilisation "incorrecte" de Monitor.Enteret Monitor.Exit(sans l'utilisation de tryet finally)
Andreas Niedermair
4
Je n'appellerais pas cela un fil zombie - c'est simplement une mauvaise utilisation de Monitor.Enteret Monitor.Exitsans une utilisation appropriée de tryet finally- 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 verrou Main... mais, hé ... peut-être que l'OP se verrouille pour un blocage au lieu de fils zombies :)
Andreas Niedermair
1
@AndreasNiedermair peut-être que la définition d'un fil zombie n'est pas tout à fait ce que je pensais. Peut-être pouvons-nous appeler cela un «thread qui a terminé l'exécution mais n'a pas libéré toutes les ressources». Tenté de supprimer et de faire mes devoirs, mais en gardant la réponse pour la ressemblance avec le scénario de l'OP.
C.Evenhuis
2
aucune offense ici! en fait, votre réponse m'a fait réfléchir :) comme l'OP parle explicitement de verrouillage non libéré, je crois que son co-compagnon a parlé de verrouillage mort - car un verrou n'est pas une ressource réelle qui est liée à un thread ( elle est partagée ... Nona) - et donc un thread « zombie » pourrait être évité en collant aux meilleures pratiques et l' utilisation lockou la bonne try/ finally-usage
Andreas Niedermair
1
@ C.Evenhuis - Je ne suis pas sûr que ma définition soit exacte. Une partie de la raison pour laquelle je pose la question est de clarifier les choses. Je pense que le concept est largement mentionné en C / C ++
smartcaveman
24

Il 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:

internal class Zombie
{
    private static readonly List<Zombie> _undead = new List<Zombie>();

    ~Zombie()
    {
        _undead.Add(this);
    }
}

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:

class Program
{
    static void Main(string[] args)
    {
        for (var i = 0; i < 150; i++)
        {
            CreateImage();
        }

        GC.Collect();

        //Something to do while the GC runs
        FindPrimeNumber(1000000);

        foreach (var zombie in Zombie.Undead)
        {
            //object is still accessable, image isn't
            zombie.Image.Save(@"C:\temp\x.png");
        }

        Console.ReadLine();
    }

    //Borrowed from here
    //http://stackoverflow.com/a/13001749/969613
    public static long FindPrimeNumber(int n)
    {
        int count = 0;
        long a = 2;
        while (count < n)
        {
            long b = 2;
            int prime = 1;// to check if found a prime
            while (b * b <= a)
            {
                if (a % b == 0)
                {
                    prime = 0;
                    break;
                }
                b++;
            }
            if (prime > 0)
                count++;
            a++;
        }
        return (--a);
    }

    private static void CreateImage()
    {
        var zombie = new Zombie(new Bitmap(@"C:\temp\a.png"));
        zombie.Image.Save(@"C:\temp\b.png");
    }
}

internal class Zombie
{
    public static readonly List<Zombie> Undead = new List<Zombie>();

    public Zombie(Image image)
    {
        Image = image;
    }

    public Image Image { get; private set; }

    ~Zombie()
    {
        Undead.Add(this);
    }
}

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!

JMK
la source
1
C'est intéressant. Est _undeadcensé être statique?
smartcaveman
1
Je l'ai donc essayé, avec une impression de l'objet "détruit". Il le traite comme un objet normal. Y a-t-il quelque chose de problématique à faire cela?
smartcaveman
1
J'ai mis à jour ma réponse avec un exemple qui, espérons-le, démontre un peu plus clairement le problème.
JMK
1
Je ne sais pas pourquoi tu as voté. Je l'ai trouvé utile. Voici une question - quel genre d'exception allez-vous obtenir? Je ne m'attends pas à ce que ce soit un NullReferenceException, car j'ai l'impression que la chose manquante doit être plus liée à la machine qu'à l'application. Est-ce correct?
smartcaveman
4
Ce n'est sûrement pas tant IDisposablele problème. Je m'inquiète des finaliseurs (destructeurs), mais le simple fait de IDisposablene 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ù IDisposableest 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 valablement IDisposablepour nettoyer les abonnements et peut appeler d'autres ressources en aval. (Je n'ai pas non plus downvote soit btw ...)
James World
21

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.

James World
la source
2
Je pense que différents ensembles d'expériences nous rendent paranoïaques à propos de différents bugs. Il a reconnu qu'il s'agissait d'un cas extrême de bord tout en l'expliquant, mais si c'est vraiment un cas de bord et non un cas jamais, j'aimerais au moins le comprendre un peu mieux - Merci pour votre contribution
smartcaveman
1
C'est suffisant. Je ne voudrais tout simplement pas que vous vous inquiétiez de manière disproportionnée de la lockdéclaration!
James World
1
Je serais beaucoup moins inquiet si j'avais une meilleure compréhension de cette question.
smartcaveman
4
J'ai voté positivement parce que vous essayez de vous débarrasser de certains thread-FUD, dont il y en a beaucoup trop. Lock-FUD va demander plus de travail pour s'en débarrasser :) Je grince des dents quand les développeurs prennent un spinlock illimité, (pour des performances), afin qu'ils puissent copier 50K de données dans une large file d'attente.
Martin James
3
Oui, à la fois au niveau général et particulier. Écrire un code sans verrou correct est aussi difficile que la programmation. D'après mon expérience dans la grande majorité des cas, les gros lockblocs 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.
James World
3

1.Y a-t-il une définition plus claire d'un "fil zombie" que ce que j'ai expliqué ici?

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!

2.Les threads zombies peuvent-ils se produire sur .NET? (Pourquoi pourquoi pas?)

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.

SH
la source
1
Mais la façon dont MSDN utilise zombie est très différente de la façon dont cette question l'utilise.
Ben Voigt
1
Oh oui, je suis d'accord, mais je faisais remarquer que même MSDN fait référence aux threads comme des threads zombies lorsqu'ils sont morts. et qu'ils peuvent en fait se produire.
SH
4
Bien sûr, mais cela fait référence à un autre code contenant toujours une poignée sur le thread, et non pas aux threads contenant les poignées sur les ressources à sa sortie. Votre première phrase, en accord avec la définition de la question, est ce qui ne va pas.
Ben Voigt
1
Ahh je vois votre point. Pour être honnête, je ne l'ai même pas remarqué. Je me concentrais sur son point que la définition existe, quelle que soit la façon dont elle se produit. N'oubliez pas que la définition existe en raison de ce qui arrive au thread et non de la façon dont elle est effectuée.
SH
0

Je peux faire des fils zombies assez facilement.

var zombies = new List<Thread>();
while(true)
{
    var th = new Thread(()=>{});
    th.Start();
    zombies.Add(th);
}

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 pas lockautour d'un objet. Mais pourquoi voudriez-vous faire ça?

Joshua
la source