399 votes

Zombies existent-ils... en .NET ?

J'ai eu une discussion avec un coéquipier sur le verrouillage .NET. Il est vraiment quelqu'un de brillant, avec une vaste expérience de niveau inférieur et le niveau supérieur de la programmation, mais son expérience avec le plus faible niveau de programmation dépasse de loin la mienne. De toute façon, Il fait valoir que .NET de verrouillage doit être évitée sur les systèmes critiques devraient être sous une lourde charge, si possible, pour éviter que la petite, certes, la possibilité d'un "zombie thread" panne d'un système. J'utilise régulièrement de verrouillage et je ne sais pas ce qu'est un "zombie fil" a été, j'ai donc demandé. L'impression que j'ai eue de son explication est qu'un zombie fil est un fil qui a été résilié, mais en quelque sorte détient toujours sur certaines ressources. Un exemple qu'il a donné de la façon dont un zombie thread pourrait casser un système a un thread commence une procédure après le verrouillage d'un objet, et puis, à un certain point résilié avant que le verrou peut être libéré. Cette situation a le potentiel de provoquer une panne du système, parce que finalement, les tentatives pour mettre en œuvre cette méthode entraînera dans les fils d'attente pour accéder à un objet qui ne sera jamais retourné, parce que le fil qui est à l'aide de l'objet verrouillé est mort.

Je pense que j'ai compris l'essentiel, mais si je suis hors de la base, s'il vous plaît laissez-moi savoir. Le concept fait sens pour moi. Je n'étais pas complètement convaincu que c'était un véritable scénario qui pourrait se produire dans .NET. Je n'ai jamais encore entendu parler de "zombies", mais je dois reconnaître que les programmeurs qui ont travaillé en profondeur dans les niveaux inférieurs ont tendance à avoir une compréhension plus profonde de l'informatique de base (comme le filetage). Je certainement ne voient la valeur de verrouillage, cependant, et j'ai vu beaucoup de classe mondiale programmeurs levier de verrouillage. J'ai aussi une capacité limitée pour évaluer cela pour moi parce que je sais que l' lock(obj) déclaration est vraiment juste sucre syntaxique pour:

bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }

et parce qu' Monitor.Enter et Monitor.Exit sont marquées extern. Il semble concevable que .NET ne fait que certains types de traitement qui protège les fils de l'exposition aux composants du système que pouvait avoir ce genre d'impact, mais c'est purement spéculatif, et probablement juste, fondée sur le fait que je n'ai jamais entendu parler de "zombie fils" avant de. Donc, j'espère que je peux obtenir des commentaires sur ce sujet ici:

  1. Est-il une meilleure définition de "zombie fil" que ce que j'ai expliqué ici?
  2. Pouvez zombie fils se produire .NET? (Pourquoi/Pourquoi pas?)
  3. Le cas échéant, Comment pourrais-je forcer la création d'un zombie fil .NET?
  4. Le cas échéant, Comment puis-je le levier de verrouillage sans risquer un zombie fil du scénario .NET?

247voto

Justin Points 42106
  • Est-il une meilleure définition de "zombie fil" que ce que j'ai expliqué ici?

Semble être une très bonne explication pour moi - un fil qui s'est arrêté (et ne peut donc plus la libération des ressources), mais dont les ressources (p. ex. poignées) sont toujours là et (potentiellement) à l'origine des problèmes.

  • Pouvez zombie fils se produire .NET? (Pourquoi/Pourquoi pas?)
  • Le cas échéant, Comment pourrais-je forcer la création d'un zombie fil .NET?

Ils ont bien, regardez, j'en ai fait une!

[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 Target qui ouvre un fichier, puis immédiatement se tue elle-même à l'aide de ExitThread. La résultante de zombie thread ne sera jamais relâcher la poignée à l' "test.txt" fichier et le fichier reste ouvert jusqu'à ce que le programme se termine (vous pouvez vérifier avec process explorer ou similaire). La poignée "test.txt" ne sera pas libéré jusqu'à ce qu' GC.Collect est appelé, il s'avère qu'il est encore plus difficile que je le pensais à créer un zombie fil qui les fuites poignées)

  • Le cas échéant, Comment puis-je le levier de verrouillage sans risquer un zombie fil du scénario .NET?

Ne faites pas ce que je viens de faire!

Tant que votre code nettoie après lui-même correctement (utilisation Sécuritaire des Poignées ou l'équivalent des classes si l'on travaille avec des ressources non managées), et tant que vous ne sortez pas de votre façon de tuer le fils dans le monde étrange et merveilleux des moyens (plus sûr moyen est tout simplement de ne jamais tuer, des filets de les laisser mettre fin eux-mêmes normalement, ou par le biais d'exceptions si nécessaire), la seule façon que vous allez avoir quelque chose qui ressemble à un zombie fil est si quelque chose va très mal (par exemple, quelque chose va mal dans le CLR).

En fait, il est étonnamment difficile de créer un zombie thread (j'ai du P/Invoke dans une fonction qui esentially vous l'indique dans la documentation de ne pas appeler à l'extérieur de C). Par exemple la suite (horrible) du code ne fait pas de créer un zombie fil.

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 affreux erreurs, la poignée de "test.txt" est encore fermé dès qu' Abort est appelé (dans le cadre de l'outil de finalisation pour file qui sous les couvertures utilise SafeFileHandle pour envelopper son descripteur de fichier)

Le verrouillage exemple dans C. Evenhuis réponse est probablement la meilleure façon d'échouer à la libération d'une ressource (un verrou dans ce cas) lorsqu'un thread est terminée par un non-façon bizarre, mais c'est facilement résolu en utilisant un lock déclaration à la place, ou de mettre la sortie en finally bloc.

Voir aussi

47voto

Jerahmeel Points 504

J'ai nettoyé ma réponse un peu, mais à gauche de l'original ci-dessous pour référence

C'est la première fois que j'ai entendu parler de ce terme zombies donc je vais assumer sa définition est la suivante:

Un thread qui a été résilié sans relâcher l'ensemble de ses ressources

Donc, compte tenu de cette définition, alors oui, vous pouvez le faire .NET, comme avec d'autres langages (C/C++, java).

Cependant, je ne pense pas que c'est une bonne raison de ne pas écrire filetée, essentielles à la mission de code .NET. Il peut y avoir d'autres raisons pour décider de la contre .NET, mais l'écriture off .NET tout simplement parce que vous pouvez avoir zombie threads en quelque sorte, ne fait pas de sens pour moi. Zombie threads sont possibles en C/C++ (j'irais même jusqu'à dire qu'il est beaucoup plus facile de gâcher dans C) et de beaucoup de critiques, avec des applications en C/C++ (high volume de négociation, les bases de données etc).

Conclusion Si vous êtes dans le processus de décision sur la langue à utiliser, alors je vous suggère de tenir la grande image en considération: performances, compétences de l'équipe, le calendrier, l'intégration avec les applications existantes, etc. Bien sûr, zombie threads sont quelque chose que vous devriez penser, mais comme il est si difficile de réellement faire cette erreur .NET par rapport à d'autres langages tels que le C, je pense que cette préoccupation va être éclipsé par d'autres choses comme celles mentionnées ci-dessus. Bonne chance!

Réponse Originale À Cette Question Zombies peut exister si vous n'écrivez pas bon de filetage code. Le même est vrai pour d'autres langages comme C/C++ et Java. Mais ce n'est pas une raison pour ne pas écrire fileté code .NET.

Et tout comme avec toute autre langue, à savoir le prix avant d'utiliser quelque chose. Il permet également de savoir ce qui se passe sous le capot de sorte que vous pouvez prévoir les problèmes potentiels.

Code fiable pour les systèmes essentiels à la mission n'est pas facile à écrire, quelle que soit la langue que vous utilisez. Mais je suis positif, il n'est pas impossible de le faire correctement .NET. Aussi autant que je sache, .NET le filetage n'est pas différent de filetage en C/C++, il utilise (ou est construit à partir de) le même système d'appels, sauf pour certains .net spécifiques des constructions (comme le poids léger de versions de la RWL et des classes d'événements).

†lapremière fois que j'ai entendu parler de ce terme zombies mais sur la base de votre description, votre collègue signifiait probablement un thread qui s'est terminée sans libérer toutes les ressources. Cela pourrait provoquer un blocage, une fuite de mémoire ou de certains autres mauvais effets secondaires. Ce n'est évidemment pas souhaitable, mais singulariser .NET en raison de cette possibilité n'est probablement pas une bonne idée car il est possible dans d'autres langues. J'irais même jusqu'à dire qu'il est plus facile de gâcher en C/C++ que dans .NET (surtout en C où vous n'avez pas RAII) mais beaucoup de critiques des applications écrites en C/C++? Donc, cela dépend vraiment de votre situation personnelle. Si vous voulez extraire la moindre once de vitesse à partir de votre application et que vous souhaitez obtenir comme près à nu le métal que possible, puis .NET peut - être pas la meilleure solution. Si vous êtes sur un budget serré et faire beaucoup d'interfaçage avec des services web existant .bibliothèques net/etc puis .NET peut être un bon choix.

27voto

C.Evenhuis Points 10818

Pour l'instant la plupart de ma réponse a été corrigé par les commentaires ci-dessous. Je ne vais pas supprimer la réponse parce que j'ai besoin de points de réputation car les informations dans les commentaires peut être utile pour les lecteurs.

Immortelle Bleue a souligné que, dans .NET 2.0 et jusqu' finally des blocs sont à l'abri d'abandons de thread. Et comme l'a commenté Andreas Niedermair, cela peut ne pas être un véritable zombie fil, mais l'exemple suivant montre comment avorter un thread peut causer 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, l' finally serait encore exécuté quand l' ThreadAbortException est tiré de cette façon.

Les informations suivantes, comme il s'avère, est valable uniquement pour .NET 1 et .NET 1.1:

Si à l'intérieur de l' lock() { } bloquer une autre exception se produit, et l' ThreadAbortException arrive exactement quand l' finally bloc est sur le point d'être exécuté, le verrou n'est pas libéré. Comme vous l'avez mentionné, l' lock() { } bloc est compilé en tant que:

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

Si un autre thread appelle Thread.Abort() à l'intérieur de la générées finally bloc, la serrure ne peut pas être libéré.

24voto

JMK Points 7572

Ce n'est pas sur Zombie fils, mais le livre Efficace C# a une section sur la mise en œuvre de IDisposable, (article 17), qui parle de Zombie objets que j'ai pensé que vous pourriez trouver intéressant.

Je vous recommande de lire le livre lui-même, mais l'essentiel, c'est que si vous avez une classe de mise en œuvre de IDisposable, ou contenant un Desctructor, la seule chose que vous devriez faire dans l'un des deux est en libérant les ressources. Si vous faites d'autres choses ici, alors il ya une chance que l'objet ne seront pas nettoyés, mais aussi de ne pas être accessible en aucune façon.

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é sur la liste globale en ce sens qu'elle reste en vie et en mémoire pour la durée de vie du programme, mais n'est pas accessible. Cela peut signifier que les ressources (en particulier les ressources non managées) ne peut pas être entièrement libéré, ce qui peut causer toutes sortes de problèmes potentiels.

Un exemple plus complet est ci-dessous. Par le temps de la boucle foreach est atteint, vous avez 150 objets dans la liste des morts-vivants, chacun contenant une image, mais l'image a été GC d et vous obtenez une exception si vous essayez de l'utiliser. Dans cet exemple, je reçois un ArgumentException (Paramètre n'est pas valide) quand j'ai essayer de faire quelque chose avec l'image, si j'essaie de le sauver, ou même de visualiser les 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 suis conscient que vous posiez des questions sur zombie threads en particulier, mais la question du titre est sur les zombies .net, et je me suis rappelé de ce et de la pensée d'autres personnes peuvent trouver cela intéressant!

21voto

James World Points 9394

Sur les systèmes critiques sous une lourde charge, l'écriture de code sans verrouillage est mieux principalement en raison de la performance améliorations. Regarder des trucs comme LMAX et comment il tire parti de la "mécanique de la sympathie" pour les grands débats de ce. Vous soucier de zombie fils? Je pense que c'est un cas limite c'est juste un bug d'être aplanies, et pas une assez bonne raison de ne pas utiliser lock.

Sonne plus comme votre ami, c'est de la fantaisie et de l'étalage de son knowledege d'un obscur terminologie exotique pour moi! Dans tous les temps, j'ai été l'exécution de la performance des laboratoires de Microsoft UK, je n'ai jamais venu à l'échelle d'une instance de ce problème .NET.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X