Gibt es Zombies… in .NET?

385

Ich hatte eine Diskussion mit einem Teamkollegen über das Sperren in .NET. Er ist ein wirklich kluger Kerl mit einem umfassenden Hintergrund in der Programmierung auf niedrigerer und höherer Ebene, aber seine Erfahrung mit der Programmierung auf niedrigerer Ebene übertrifft meine bei weitem. Wie auch immer, er argumentierte, dass .NET-Sperren auf kritischen Systemen vermieden werden sollten, von denen erwartet wird, dass sie unter hoher Last stehen, wenn dies überhaupt möglich ist, um die zugegebenermaßen geringe Wahrscheinlichkeit zu vermeiden, dass ein "Zombie-Thread" ein System zum Absturz bringt. Ich benutze routinemäßig das Sperren und wusste nicht, was ein "Zombiethread" ist, also fragte ich. Der Eindruck, den ich von seiner Erklärung bekam, ist, dass ein Zombiethread ein Thread ist, der beendet wurde, aber irgendwie immer noch an einigen Ressourcen festhält. Ein Beispiel, das er gab, wie ein Zombie-Thread ein System beschädigen könnte, war, dass ein Thread eine Prozedur beginnt, nachdem er ein Objekt gesperrt hat. und wird dann irgendwann beendet, bevor die Sperre aufgehoben werden kann. Diese Situation kann zum Absturz des Systems führen, da bei Versuchen, diese Methode auszuführen, alle Threads auf den Zugriff auf ein Objekt warten, das niemals zurückgegeben wird, da der Thread, der das gesperrte Objekt verwendet, tot ist.

Ich glaube, ich habe das Wesentliche verstanden, aber wenn ich nicht auf der Basis bin, lassen Sie es mich bitte wissen. Das Konzept ergab für mich einen Sinn. Ich war nicht ganz davon überzeugt, dass dies ein echtes Szenario ist, das in .NET passieren kann. Ich habe noch nie von "Zombies" gehört, aber ich erkenne, dass Programmierer, die auf niedrigeren Ebenen eingehend gearbeitet haben, tendenziell ein tieferes Verständnis der Computergrundlagen (wie Threading) haben. Ich sehe jedoch definitiv den Wert des Sperren, und ich habe viele Weltklasse-Programmierer gesehen, die das Sperren wirksam einsetzen. Ich habe auch nur begrenzte Möglichkeiten, dies für mich selbst zu bewerten, weil ich weiß, dass die lock(obj)Aussage wirklich nur syntaktischer Zucker ist für:

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

und weil Monitor.Enterund Monitor.Exitmarkiert sind extern. Es scheint denkbar, dass .NET eine Art von Verarbeitung durchführt, die Threads vor der Exposition gegenüber Systemkomponenten schützt, die diese Auswirkungen haben könnten. Dies ist jedoch rein spekulativ und basiert wahrscheinlich nur auf der Tatsache, dass ich noch nie von "Zombie-Threads" gehört habe. Vor. Ich hoffe also, dass ich hier ein Feedback dazu bekommen kann:

  1. Gibt es eine klarere Definition eines "Zombiethreads" als das, was ich hier erklärt habe?
  2. Können Zombiethreads in .NET auftreten? (Warum Warum nicht?)
  3. Wie kann ich gegebenenfalls die Erstellung eines Zombiethreads in .NET erzwingen?
  4. Wie kann ich das Sperren nutzen, ohne ein Zombie-Thread-Szenario in .NET zu riskieren?

Aktualisieren

Ich habe diese Frage vor etwas mehr als zwei Jahren gestellt. Heute ist das passiert:

Das Objekt befindet sich in einem Zombie-Zustand.

Smartcaveman
quelle
8
Sind Sie sicher, dass Ihr Kollege nicht über Deadlocking spricht?
Andreas Niedermair
10
@AndreasNiedermair - Ich weiß, was Deadlocking ist, und es war eindeutig keine Frage des Missbrauchs dieser Terminologie. Deadlocking wurde im Gespräch erwähnt und unterschied sich deutlich von einem "Zombiethread". Für mich besteht der Hauptunterschied darin, dass eine tote Sperre eine unlösbare Zwei-Wege-Abhängigkeit aufweist, während der Zombie-Thread einseitig ist und einen abgebrochenen Prozess erfordert. Wenn Sie nicht einverstanden sind und der Meinung sind, dass es eine bessere Möglichkeit gibt, diese Dinge zu betrachten, erklären Sie dies bitte
smartcaveman
19
Ich denke, der Begriff "Zombie" stammt tatsächlich aus einem UNIX-Hintergrund, wie im "Zombie-Prozess", oder ??? Es gibt eine klare Definition eines „Zombie - Prozess“ in UNIX: es beschreibt ein Kind Prozess, der beendet hat , aber wo die Eltern des Kindes Prozess muss noch „Freigabe“ der Kind - Prozess (und Ressourcen) durch den Aufruf waitoder waitpid. Der untergeordnete Prozess wird dann als "Zombie-Prozess" bezeichnet. Siehe auch howtogeek.com/119815
hogliux
9
Wenn ein Teil Ihres Programms abstürzt und das Programm in einem undefinierten Zustand bleibt, kann dies natürlich Probleme mit dem Rest Ihres Programms verursachen. Dasselbe kann passieren, wenn Sie Ausnahmen in einem Single-Thread-Programm nicht ordnungsgemäß behandeln. Das Problem liegt nicht bei Threads. Das Problem besteht darin, dass Sie einen globalen veränderlichen Status haben und die unerwartete Thread-Beendigung nicht ordnungsgemäß verarbeiten. Ihr "wirklich kluger" Mitarbeiter ist in diesem Fall völlig daneben.
Jim Mischel
9
"Seit den ersten Computern gab es immer Geister in der Maschine. Zufällige Codesegmente, die sich zu unerwarteten Protokollen zusammengeschlossen haben ..."
Chris Laplante

Antworten:

242
  • Gibt es eine klarere Definition eines "Zombiethreads" als das, was ich hier erklärt habe?

Scheint mir eine ziemlich gute Erklärung zu sein - ein Thread, der beendet wurde (und daher keine Ressourcen mehr freigeben kann), dessen Ressourcen (z. B. Handles) noch vorhanden sind und (möglicherweise) Probleme verursachen.

  • Können Zombiethreads in .NET auftreten? (Warum Warum nicht?)
  • Wie kann ich gegebenenfalls die Erstellung eines Zombiethreads in .NET erzwingen?

Sie tun es sicher, schau, ich habe eins gemacht!

[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);
    }
}

Dieses Programm startet einen Thread, Targetder eine Datei öffnet und sich dann sofort mit beendet ExitThread. Der resultierende Zombiethread gibt das Handle für die Datei "test.txt" niemals frei und daher bleibt die Datei geöffnet, bis das Programm beendet wird (Sie können dies mit dem Prozess-Explorer oder ähnlichem überprüfen). Das Handle zu "test.txt" wird erst freigegeben, wenn GC.Collectes aufgerufen wird - es stellt sich heraus, dass es noch schwieriger ist, als ich dachte, einen Zombiethread zu erstellen, der Handles verliert.

  • Wie kann ich das Sperren nutzen, ohne ein Zombie-Thread-Szenario in .NET zu riskieren?

Tu nicht was ich gerade getan habe!

Solange Ihr Code korrekt nach sich selbst bereinigt wird (verwenden Sie sichere Handles oder gleichwertige Klassen, wenn Sie mit nicht verwalteten Ressourcen arbeiten) und solange Sie sich nicht die Mühe machen, Threads auf seltsame und wunderbare Weise zu beenden (der sicherste Weg ist gerecht Um Threads niemals zu töten - lassen Sie sie sich normal beenden oder bei Bedarf durch Ausnahmen). Der einzige Weg, auf dem Sie etwas haben, das einem Zombiethread ähnelt, ist, wenn etwas sehr schief gelaufen ist (z. B. wenn in der CLR etwas schief geht).

Tatsächlich ist es überraschend schwierig, einen Zombiethread zu erstellen (ich musste P / Invoke in eine Funktion einbinden, die Ihnen in der Dokumentation im Wesentlichen sagt, dass Sie ihn nicht außerhalb von C aufrufen sollen). Zum Beispiel erzeugt der folgende (schreckliche) Code tatsächlich keinen Zombiethread.

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);
}

Trotz einiger ziemlich schrecklicher Fehler wird das Handle zu "test.txt" immer noch geschlossen, sobald Abortes aufgerufen wird (als Teil des Finalizers, für fileden unter dem Deckblatt SafeFileHandle verwendet wird , um das Dateihandle zu verpacken).

Das Sperrbeispiel in der Antwort von C.Evenhuis ist wahrscheinlich der einfachste Weg, eine Ressource (in diesem Fall eine Sperre) nicht freizugeben, wenn ein Thread auf nicht seltsame Weise beendet wird. Dies lässt sich jedoch leicht beheben, indem lockstattdessen entweder eine Anweisung oder eine Anweisung verwendet wird die Freigabe in einen finallyBlock setzen.

Siehe auch

Justin
quelle
3
Ich erinnere mich, als ich mit dem Speichern von Sachen in Excel mit einem Hintergrundarbeiter spielte, habe ich nicht die ganze Zeit alle Ressourcen freigegeben (weil ich nur das Debuggen usw. übersprungen habe). im taskmanager habe ich danach ca. 50 excel prozesse gesehen. habe ich zombieexcelprocesses erstellt?
Stefan
3
@ Justin - +1 - Tolle Antwort. Ich bin allerdings etwas skeptisch gegenüber Ihrem ExitThreadAnruf. Natürlich funktioniert es, aber es fühlt sich eher nach einem cleveren Trick als nach einem realistischen Szenario an. Eines meiner Ziele ist es zu lernen, was ich nicht tun soll, damit ich nicht versehentlich Zombiethreads mit .NET-Code erstelle. Ich hätte wahrscheinlich herausfinden können, dass das Aufrufen von C ++ - Code, von dem bekannt ist, dass er dieses Problem verursacht, aus .NET-Code den gewünschten Effekt erzielen würde. Sie wissen offensichtlich viel über dieses Zeug. Kennen Sie andere Fälle (möglicherweise seltsam, aber nicht seltsam genug, um niemals unbeabsichtigt zu passieren) mit demselben Ergebnis?
Smartcaveman
3
Die Antwort lautet also "nicht in c # 4", oder? Wenn Sie aus der CLR springen müssen, um einen Zombiethread zu erhalten, scheint dies kein .NET-Problem zu sein.
Gusdor
4
@Stefan würde ich fast definitiv nicht sagen. Ich habe das, was du beschrieben hast, oft getan. Die Excel-Prozesse sind keine Zombies, da sie noch ausgeführt werden, jedoch nicht sofort über die normale Benutzeroberfläche zugänglich sind. Sie sollten sie über Aufrufe von GetObject abrufen können . Set excelInstance = GetObject(, "Excel.Application")
Daniel
7
"Der resultierende Zombiethread gibt niemals das Handle für die Datei" test.txt "frei und die Datei bleibt geöffnet, bis das Programm beendet wird" ist falsch. Kleiner Beweis: `static void Main (string [] args) {neuer Thread (GcCollect) .Start (); neuer Thread (Ziel) .Start (); Console.ReadLine (); } private static void Target () {using (var file = File.Open ("test.txt", FileMode.OpenOrCreate)) {ExitThread (0); }} private statische Leere GcCollect () {while (true) {Thread.Sleep (10000); GC.Collect (); }} `
Sinix
46

Ich habe meine Antwort ein wenig aufgeräumt, aber das Original unten als Referenz belassen

Es ist das erste Mal, dass ich von dem Begriff Zombies höre, also gehe ich davon aus, dass seine Definition lautet:

Ein Thread, der beendet wurde, ohne alle Ressourcen freizugeben

Wenn Sie diese Definition haben, können Sie dies in .NET wie in anderen Sprachen (C / C ++, Java) tun.

Doch , ich glaube dies nicht als Grund nicht kritisch Code threaded, Mission in .NET zu schreiben. Es kann andere Gründe geben, sich gegen .NET zu entscheiden, aber das Abschreiben von .NET, nur weil Sie Zombiethreads haben können, macht für mich keinen Sinn. Zombie-Threads sind in C / C ++ möglich (ich würde sogar argumentieren, dass es viel einfacher ist, in C durcheinander zu kommen) und viele kritische Apps mit Threads sind in C / C ++ (Handel mit hohem Volumen, Datenbanken usw.).

Fazit Wenn Sie sich gerade für eine Sprache entscheiden, sollten Sie das Gesamtbild berücksichtigen: Leistung, Teamfähigkeit, Zeitplan, Integration in vorhandene Apps usw. Sicher, Zombiethreads sollten Sie sich überlegen , aber da es im Vergleich zu anderen Sprachen wie C so schwierig ist, diesen Fehler in .NET tatsächlich zu machen, denke ich, dass dieses Problem von anderen Dingen wie den oben genannten überschattet wird. Viel Glück!

Ursprüngliche Antwort Zombies können existieren, wenn Sie keinen richtigen Threading-Code schreiben. Gleiches gilt für andere Sprachen wie C / C ++ und Java. Dies ist jedoch kein Grund, keinen Thread-Code in .NET zu schreiben.

Und wie bei jeder anderen Sprache sollten Sie den Preis kennen, bevor Sie etwas verwenden. Es ist auch hilfreich zu wissen, was unter der Haube passiert, damit Sie mögliche Probleme vorhersehen können.

Zuverlässiger Code für geschäftskritische Systeme ist nicht einfach zu schreiben, egal in welcher Sprache Sie sich befinden. Aber ich bin mir sicher, dass es in .NET nicht unmöglich ist, ihn korrekt auszuführen. Auch AFAIK, .NET-Threading unterscheidet sich nicht wesentlich vom Threading in C / C ++, es verwendet (oder wird daraus erstellt) dieselben Systemaufrufe mit Ausnahme einiger .net-spezifischer Konstrukte (wie die Lightweight-Versionen von RWL und Ereignisklassen).

† Als ich zum ersten Mal von dem Begriff Zombies gehört habe, meinte Ihr Kollege aufgrund Ihrer Beschreibung wahrscheinlich einen Thread, der beendet wurde, ohne alle Ressourcen freizugeben. Dies kann möglicherweise einen Deadlock, einen Speicherverlust oder eine andere schlimme Nebenwirkung verursachen. Dies ist offensichtlich nicht wünschenswert, aber das Herausgreifen von .NET aufgrund dieser Möglichkeit ist wahrscheinlich keine gute Idee, da dies auch in anderen Sprachen möglich ist. Ich würde sogar argumentieren, dass es in C / C ++ einfacher ist, Fehler zu machen als in .NET (besonders in C, wo Sie kein RAII haben), aber viele kritische Apps sind in C / C ++ geschrieben, oder? Es hängt also wirklich von Ihren individuellen Umständen ab. Wenn Sie jede Unze Geschwindigkeit aus Ihrer Anwendung extrahieren und so nah wie möglich an Bare Metal kommen möchten, ist .NET möglicherweise die richtige Wahlnicht die beste Lösung sein. Wenn Sie ein knappes Budget haben und viele Schnittstellen zu Webdiensten / vorhandenen .net-Bibliotheken / usw. herstellen, ist .NET möglicherweise eine gute Wahl.

Jerahmeel
quelle
(1) Ich bin mir nicht sicher, wie ich herausfinden soll, was unter der Haube passiert, wenn ich auf Sackgassen externstoße. Wenn Sie einen Vorschlag haben, würde ich ihn gerne hören. (2) Ich würde zustimmen, dass dies in .NET möglich ist. Ich würde gerne glauben, dass es mit dem Sperren möglich ist, aber ich habe noch keine zufriedenstellende Antwort gefunden, um dies heute zu rechtfertigen
smartcaveman
1
@smartcaveman Wenn Sie lockingdas lockSchlüsselwort meinen , dann wahrscheinlich nicht, da es die Ausführung serialisiert. Um den Durchsatz zu maximieren, müssten Sie abhängig von den Eigenschaften Ihres Codes die richtigen Konstrukte verwenden. Ich würde sagen, wenn Sie zuverlässigen Code in c / c ++ / java / was auch immer mit pthreads / boost / thread pools / what schreiben können, dann können Sie ihn auch in C # schreiben. Aber wenn Sie mit keiner Bibliothek zuverlässigen Code in einer Sprache schreiben können, bezweifle ich, dass das Schreiben in C # anders sein wird.
Jerahmeel
@smartcaveman Wie um herauszufinden, was unter der Haube ist, hilft Google Haufen und wenn Ihr Problem zu exotisch ist, um es im Web zu finden, ist Reflektor sehr praktisch. Aber für die Threading-Klassen finde ich die MSDN-Dokumentation sehr nützlich. Und viele davon sind nur Wrapper für dieselben Systemaufrufe, die Sie in C anway verwenden.
Jerahmeel
1
@smartcaveman überhaupt nicht, das habe ich nicht gemeint. Es tut mir leid, wenn es so rüberkam. Was ich sagen wollte ist, dass Sie beim Schreiben einer kritischen Anwendung nicht zu schnell .NET abschreiben sollten. Sicher, Sie könnten Dinge tun, die vielleicht viel schlimmer sind als Zombiethreads (die meiner Meinung nach nur Threads sind, die keine nicht verwalteten Ressourcen freigegeben haben , was in anderen Sprachen völlig vorkommen kann: stackoverflow.com/questions/14268080/… ), aber Auch dies bedeutet nicht, dass .NET keine praktikable Lösung ist.
Jerahmeel
2
Ich denke nicht, dass .NET überhaupt eine schlechte Technologie ist. Es ist eigentlich mein primäres Entwicklungsframework. Aus diesem Grund denke ich, dass es wichtig ist, die Fehler zu verstehen, für die es anfällig ist. Grundsätzlich singe ich .NET aus, aber weil es mir gefällt, nicht weil ich es nicht mag. (Und keine Sorge, Bruder, ich + 1-ed Sie)
Smartcaveman
26

Im Moment wurde der größte Teil meiner Antwort durch die folgenden Kommentare korrigiert. Ich werde die Antwort nicht löschen, da ich die Reputationspunkte benötige, da die Informationen in den Kommentaren für die Leser wertvoll sein können.

Immortal Blue wies darauf hin, dass in .NET 2.0 und höher finallyBlöcke immun gegen Thread-Abbrüche sind. Und wie von Andreas Niedermair kommentiert, ist dies möglicherweise kein tatsächlicher Zombiethread, aber das folgende Beispiel zeigt, wie das Abbrechen eines Threads Probleme verursachen kann:

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");
    }
}

Bei Verwendung eines lock() { }Blocks wird der Block finallyjedoch weiterhin ausgeführt, wenn a auf ThreadAbortExceptiondiese Weise ausgelöst wird.

Wie sich herausstellt, gelten die folgenden Informationen nur für .NET 1 und .NET 1.1:

Wenn innerhalb des lock() { }Blocks eine andere Ausnahme auftritt und ThreadAbortExceptiongenau dann eintrifft, wenn der finallyBlock ausgeführt werden soll, wird die Sperre nicht aufgehoben. Wie Sie bereits erwähnt haben, wird der lock() { }Block wie folgt kompiliert:

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

Wenn ein anderer Thread Thread.Abort()innerhalb des generierten finallyBlocks aufruft , wird die Sperre möglicherweise nicht aufgehoben.

C.Evenhuis
quelle
2
Sie sprechen darüber, lock()aber ich kann keine Verwendung davon sehen ... also - wie ist das verbunden? Dies ist eine "falsche" Verwendung von Monitor.Enterund Monitor.Exit(ohne die Verwendung von tryund finally)
Andreas Niedermair
4
Ich würde das nicht als Zombie-Thread bezeichnen - dies ist einfach eine falsche Verwendung von Monitor.Enterund Monitor.Exitohne ordnungsgemäße Verwendung von tryund finally- Ihr Szenario sperrt ohnehin andere Threads, an denen möglicherweise festgehalten wird _lock, sodass es ein Deadlock-Szenario gibt - nicht unbedingt einen Zombie-Thread ... Außerdem lösen Sie die Sperre nicht in Main... aber hey ... vielleicht sperrt das OP wegen Deadlocking anstelle von Zombiethreads :)
Andreas Niedermair
1
@AndreasNiedermair Vielleicht ist die Definition eines Zombiethreads nicht ganz das, was ich dachte. Vielleicht können wir dies einen "Thread nennen, der die Ausführung beendet hat, aber nicht alle Ressourcen freigegeben hat". Ich bin versucht, meine Hausaufgaben zu löschen und zu erledigen, aber die Antwort für die Ähnlichkeit mit dem OP-Szenario beizubehalten.
C.Evenhuis
2
nichts für ungut hier! Eigentlich hat mich deine Antwort nur dazu gebracht, darüber nachzudenken :) Da das OP explizit davon spricht, dass Sperren nicht freigegeben werden, glaube ich, dass sein Kollege über Deadlocking gesprochen hat - weil ein Schloss keine echte Ressource ist, die an einen Thread gebunden ist ( es ist geteilt ... Nona) - und damit jeder „Zombie“ Faden durch das Festhalten an bewährten Verfahren vermieden werden könnten und mit lockoder richtige try/ finally-usage
Andreas Niedermair
1
@ C.Evenhuis - Ich bin nicht sicher, ob meine Definition korrekt ist. Ein Grund, warum ich die Frage stelle, ist, dies zu klären. Ich denke, dass das Konzept in C / C ++
Smartcaveman
24

Hier geht es nicht um Zombie-Threads, aber das Buch Effective C # enthält einen Abschnitt zur Implementierung von IDisposable (Punkt 17), in dem es um Zombie-Objekte geht, die ich für interessant hielt.

Ich empfehle, das Buch selbst zu lesen, aber das Wesentliche ist, dass Sie, wenn Sie eine Klasse haben, die entweder IDisposable implementiert oder einen Desctructor enthält, nur Ressourcen freigeben sollten. Wenn Sie hier andere Dinge tun, besteht die Möglichkeit, dass das Objekt nicht durch Müll gesammelt wird, sondern auch in keiner Weise zugänglich ist.

Es gibt ein ähnliches Beispiel wie unten:

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

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

Wenn der Destruktor für dieses Objekt aufgerufen wird, wird ein Verweis auf sich selbst in die globale Liste aufgenommen, was bedeutet, dass er während der gesamten Lebensdauer des Programms am Leben bleibt und im Speicher bleibt, jedoch nicht zugänglich ist. Dies kann dazu führen, dass Ressourcen (insbesondere nicht verwaltete Ressourcen) möglicherweise nicht vollständig freigegeben werden, was zu allen möglichen Problemen führen kann.

Ein vollständigeres Beispiel finden Sie unten. Bis die foreach-Schleife erreicht ist, befinden sich 150 Objekte in der Liste der Untoten, die jeweils ein Bild enthalten. Das Bild wurde jedoch einer GC unterzogen, und Sie erhalten eine Ausnahme, wenn Sie versuchen, es zu verwenden. In diesem Beispiel wird eine ArgumentException angezeigt (Parameter ist ungültig), wenn ich versuche, etwas mit dem Bild zu tun, unabhängig davon, ob ich es speichern oder sogar Abmessungen wie Höhe und Breite anzeigen möchte:

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);
    }
}

Ich bin mir wieder bewusst, dass Sie insbesondere nach Zombiethreads gefragt haben, aber der Fragentitel bezieht sich auf Zombies in .net, und ich wurde daran erinnert und dachte, andere könnten es interessant finden!

JMK
quelle
1
Das ist interessant. Soll _undeadstatisch sein?
Smartcaveman
1
Also habe ich es versucht, indem ich das "zerstörte" Objekt ausgedruckt habe. Es behandelt es wie ein normales Objekt. Ist das problematisch?
Smartcaveman
1
Ich habe meine Antwort mit einem Beispiel aktualisiert, das das Problem hoffentlich etwas deutlicher zeigt.
JMK
1
Ich weiß nicht, warum du herabgestimmt wurdest. Ich fand es hilfreich. Hier ist eine Frage - welche Art von Ausnahme werden Sie bekommen? Ich erwarte nicht, dass es eine sein wird NullReferenceException, weil ich das Gefühl habe, dass das fehlende Ding mehr an die Maschine als an die Anwendung gebunden sein muss. Ist das richtig?
Smartcaveman
4
Sicherlich geht es nicht so sehr IDisposabledarum. Ich habe Bedenken IDisposablewegen Finalisierern (Destruktoren), aber wenn ich ein Objekt habe , wird es nicht in die Finalizer-Warteschlange gestellt und riskiert dieses Zombieszenario. Diese Warnung betrifft Finalizer und sie können Dispose-Methoden aufrufen. Es gibt Beispiele, bei denen IDisposableTypen ohne Finalizer verwendet werden. Die Stimmung sollte für die Bereinigung von Ressourcen sein, aber das könnte eine nicht triviale Bereinigung von Ressourcen sein. RX wird gültig IDisposablezum Bereinigen von Abonnements verwendet und kann andere nachgelagerte Ressourcen aufrufen. (Ich habe übrigens auch nicht abgelehnt ...)
James World
21

Auf kritischen Systemen unter hoher Last ist das Schreiben von Code ohne Sperren vor allem aufgrund der Leistungsverbesserungen besser. Schauen Sie sich Dinge wie LMAX an und wie es "mechanische Sympathie" für großartige Diskussionen darüber nutzt. Sorgen Sie sich um Zombiethreads? Ich denke, das ist ein Randfall, der nur ein Fehler ist, der ausgebügelt werden muss, und kein Grund genug, ihn nicht zu verwenden lock.

Klingt eher so, als ob Ihr Freund nur ausgefallen ist und mir sein Wissen über obskure exotische Terminologie zur Schau stellt! Während der gesamten Zeit, in der ich die Performance Labs bei Microsoft UK leitete, bin ich in .NET nie auf eine Instanz dieses Problems gestoßen.

James World
quelle
2
Ich denke, unterschiedliche Erfahrungen machen uns in Bezug auf verschiedene Fehler paranoid. Er räumte ein, dass es sich bei der Erklärung um einen extremen Randfall handelt, aber wenn es sich wirklich um einen
Randfall handelt
1
Meinetwegen. Ich möchte nur nicht, dass Sie sich überproportional Sorgen um die lockAussage machen!
James World
1
Ich wäre viel weniger besorgt, wenn ich dieses Problem besser verstehen würde.
Smartcaveman
4
Ich habe gestimmt, weil Sie versuchen, einige Thread-FUD loszuwerden, von denen es viel zu viel gibt. Lock-FUD wird mehr Arbeit in Anspruch nehmen :) Ich erschrecke nur, wenn Entwickler einen unbegrenzten Spinlock (aus Gründen der Leistung) verwenden, damit sie 50.000 Daten in eine breite Warteschlange kopieren können.
Martin James
3
Ja, sowohl allgemein als auch speziell. Das Schreiben von korrektem Code ohne Sperren ist ungefähr so ​​schwierig wie das Programmieren. Nach meiner Erfahrung sind in den allermeisten Fällen große, fette, körnige (relativ) leicht zu verstehende lockBlöcke in Ordnung. Ich würde es vermeiden, Komplexität zum Zwecke der Leistungsoptimierung einzuführen, bis Sie wissen, dass Sie dies benötigen.
James World
3

1. Gibt es eine klarere Definition eines "Zombiethreads" als das, was ich hier erklärt habe?

Ich bin damit einverstanden, dass "Zombie-Threads" existieren. Dies ist ein Begriff, der sich darauf bezieht, was mit Threads passiert, die über Ressourcen verfügen, die sie nicht loslassen und dennoch nicht vollständig sterben, daher der Name "Zombie" Erklärung dieser Überweisung ist ziemlich richtig auf dem Geld!

2. Können Zombiethreads in .NET auftreten? (Warum Warum nicht?)

Ja, sie können auftreten. Es ist eine Referenz und wird von Windows tatsächlich als "Zombie" bezeichnet: MSDN verwendet das Wort "Zombie" für tote Prozesse / Threads

Häufig passiert es eine andere Geschichte und hängt von Ihren Codierungstechniken und -praktiken ab, wie für Sie, die Thread Locking mögen und es für eine Weile getan haben, würde ich mir nicht einmal Sorgen machen, dass Ihnen dieses Szenario passiert.

Und ja, wie @KevinPanko in den Kommentaren richtig erwähnt hat, stammen "Zombie-Threads" von Unix, weshalb sie in XCode-ObjectiveC verwendet und als "NSZombie" bezeichnet und zum Debuggen verwendet werden. Es verhält sich fast genauso ... Der einzige Unterschied besteht darin, dass ein Objekt, das hätte sterben sollen, anstelle des "Zombie-Threads", der ein potenzielles Problem in Ihrem Code darstellen könnte, zu einem "ZombieObject" zum Debuggen wird.

Sch
quelle
1
Aber die Art und Weise, wie MSDN Zombie verwendet, unterscheidet sich stark von der Art und Weise, wie diese Frage es verwendet.
Ben Voigt
1
Oh ja, ich stimme zu, aber ich habe darauf hingewiesen, dass sogar MSDN Threads als Zombie-Threads bezeichnet, wenn sie tot sind. und dass sie tatsächlich auftreten können.
SH
4
Sicher, aber es bezieht sich auf einen anderen Code, der immer noch ein Handle für den Thread enthält, nicht auf den Thread, der beim Beenden Handles für Ressourcen enthält. Ihr erster Satz, der mit der Definition in der Frage übereinstimmt, ist falsch.
Ben Voigt
1
Ahh ich verstehe deinen Standpunkt. Um ehrlich zu sein, habe ich das gar nicht bemerkt. Ich habe mich auf seinen Punkt konzentriert, dass die Definition existiert, unabhängig davon, wie es passiert. Denken Sie daran, dass die Definition aufgrund dessen existiert, was mit dem Thread passiert und nicht wie es gemacht wird.
SH
0

Ich kann leicht genug Zombiefäden machen.

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

Dadurch lecken die Gewindegriffe (für Join()). Für uns in der verwalteten Welt ist es nur ein weiterer Speicherverlust.

Nun, ein Faden so zu töten, dass er tatsächlich Schlösser hält, ist ein Schmerz im Heck, aber möglich. Der andere ExitThread()macht den Job. Wie er herausfand, wurde das Dateihandle vom GC aufgeräumt, lockein Objekt um ein Objekt jedoch nicht. Aber warum würdest du das tun?

Joshua
quelle