Czy Zombie istnieją ... in.NET?

Prowadziłem dyskusję z kolegą z zespołu na temat locking in. NET. jest naprawdę bystrym facetem z rozległym doświadczeniem zarówno w programowaniu niższym, jak i wyższym, ale jego doświadczenie z programowaniem niższym znacznie przewyższa moje. W każdym razie argumentował, że należy unikać blokowania. net na krytycznych systemach, które powinny być pod dużym obciążeniem, jeśli w ogóle jest to możliwe, aby uniknąć wprawdzie niewielkiej możliwości" wątku zombie " upaść system. Rutynowo używam blokady i nie wiedziałem, co to jest "wątek zombie", więc zapytałem. Odniosłem wrażenie z jego wyjaśnienia, że wątek zombie to wątek, który się zakończył, ale jakoś nadal trzyma się niektórych zasobów. Przykład jak wątek zombie może złamać system, to wątek rozpoczyna jakąś procedurę po zablokowaniu jakiegoś obiektu, a następnie jest w pewnym momencie zakończony, zanim blokada może zostać zwolniona. Ta sytuacja może spowodować awarię systemu, ponieważ w końcu, próby wykonania tego metoda spowoduje, że wszystkie wątki będą czekać na dostęp do obiektu, który nigdy nie zostanie zwrócony, ponieważ wątek, który używa zablokowanego obiektu, jest martwy.

Myślę, że zrozumiałem sedno tego, ale jeśli jestem poza bazą, proszę dać mi znać. Ta koncepcja miała dla mnie sens. Nie byłem do końca przekonany, że jest to prawdziwy scenariusz, który może się wydarzyć w .NET. nigdy wcześniej nie słyszałem o "zombie" , ale zdaję sobie sprawę, że programiści, którzy pracowali głębiej na niższych poziomach mają tendencję do głębsze zrozumienie podstaw obliczeniowych (takich jak threading). Zdecydowanie widzę wartość w blokowaniu, jednak i widziałem wielu światowej klasy programistów dźwigni blokowania. Mam również ograniczoną zdolność do oceny tego dla siebie, ponieważ Wiem, że lock(obj) stwierdzenie jest tak naprawdę tylko cukrem składniowym dla:
bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }

I dlatego, że Monitor.Enter i Monitor.Exit są oznaczone extern. Wydaje się możliwe, że. NET wykonuje jakiś rodzaj przetwarzania, który chroni wątki przed narażeniem na składniki systemu to może mieć taki wpływ, ale jest to czysto spekulacyjne i prawdopodobnie oparte na fakcie, że nigdy wcześniej nie słyszałem o "wątkach zombie". Mam nadzieję, że uda mi się uzyskać kilka opinii na ten temat tutaj:

  1. czy istnieje jaśniejsza definicja "wątku zombie" niż to, co wyjaśniłem tutaj?
  2. czy wątki zombie mogą występować na. Net? (Dlaczego/dlaczego nie?)
  3. Jeśli dotyczy, Jak mogę wymusić utworzenie wątku zombie w. NET?]}
  4. jeśli dotyczy, Jak mogę blokowanie dźwigni bez ryzyka scenariusza wątku zombie w. NET?]}

Update

Zadałem to pytanie nieco ponad dwa lata temu. Dzisiaj tak się stało:

Obiekt jest w stanie zombie.

Author: smartcaveman, 2013-11-19

7 answers

  • czy istnieje jaśniejsza definicja "wątku zombie" niż to, co wyjaśniłem tutaj?

Wydaje mi się całkiem dobrym wyjaśnieniem-wątek, który się zakończył( i dlatego nie może już udostępniać żadnych zasobów), ale którego zasoby (np. uchwyty) nadal istnieją i (potencjalnie) powodują problemy.

  • czy wątki zombie mogą występować na. Net? (Dlaczego/dlaczego nie?)
  • jeśli dotyczy, Jak mogę wymusić utworzenie wątek zombie w. NET?

Na pewno, spójrz, zrobiłem jeden!

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

Ten program uruchamia wątek Target, który otwiera plik, a następnie natychmiast zabija się za pomocą ExitThread. powstały wątek zombie nigdy nie zwolni uchwytu do "testu.plik txt " i tak plik pozostanie otwarty do momentu zakończenia programu (możesz sprawdzić za pomocą process explorer lub podobnego). uchwyt do " test.txt" nie zostanie wydany dopóki GC.Collect nie zostanie wywołany-zmieni Na zewnątrz jest jeszcze trudniejsze niż myślałem, aby stworzyć wątek zombie, który przecieka uchwyty)

  • jeśli ma to zastosowanie, w jaki sposób mogę wykorzystać blokowanie bez ryzyka scenariusza wątku zombie w. NET?
Nie rób tego, co ja!

Tak długo, jak Twój kod czyści się po sobie poprawnie (używaj bezpiecznych uchwytów lub równoważnych klas, jeśli pracujesz z niezarządzanymi zasobami), i tak długo, jak nie będziesz się starał zabijać wątków w dziwnych i wonderful ways (najbezpieczniejszym sposobem jest po prostu nigdy nie zabijać wątków-pozwól im zakończyć się normalnie, lub poprzez wyjątki, jeśli to konieczne), jedynym sposobem, aby mieć coś przypominającego wątek zombie jest, jeśli coś poszło bardzo źle (np. coś poszło nie tak w CLR).

W rzeczywistości jest to zaskakująco trudne do stworzenia wątku zombie(musiałem P / wywołać do funkcji, która esentially mówi ci w dokumentacji, aby nie wywoływać go poza C). Na przykład poniższy (okropny) kod nie tworzy wątku 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);
}

Pomimo popełniania okropnych błędów, uchwyt do " testowania.txt " jest nadal zamknięty, gdy tylko Abort jest wywołany (jako część finalizera dla file, który pod okładkami używa SafeFileHandle do owinięcia uchwytu plików)

Przykład blokowania w C. odpowiedź Evenhuis jest prawdopodobnie najprostszym sposobem, aby nie zwolnić zasobu (blokada w tym przypadku), gdy wątek jest rozwiązane w nie-dziwny sposób, ale łatwo to naprawić, używając zamiast tego instrukcji lock, lub umieszczając release w bloku finally.

Zobacz też

 231
Author: Justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:18

trochę wyczyściłem swoją odpowiedź, ale zostawiłem oryginalną poniżej dla odniesienia

Pierwszy raz słyszę o pojęciu zombie, więc zakładam, że jego definicja brzmi:

Wątek, który zakończył się bez uwolnienia wszystkich swoich zasobów

Więc biorąc pod uwagę tę definicję, to tak, można to zrobić w. NET, jak w innych językach(C / C++, java).

Jednak, nie uważam tego za dobry powód, aby nie pisać threaded, mission critical kod w .NET. mogą być inne powody, aby zdecydować się na. Net, ale odpisywanie. NET tylko dlatego, że można mieć wątki zombie jakoś nie ma dla mnie sensu. Wątki Zombie są możliwe w C / C++ (powiedziałbym nawet, że dużo łatwiej jest popsuć w C), a wiele krytycznych, wątkowych aplikacji jest w C / C++ (handel wielkimi wolumenami, bazy danych itp.).

Wniosek Jeśli jesteś w trakcie decydowania o języku, który chcesz użyć, proponuję wziąć pod uwagę szerszy obraz: wydajność, umiejętności zespołu, harmonogram, integracja z istniejącymi aplikacjami itp. Oczywiście, wątki zombie są czymś, o czym powinieneś pomyśleć, ale ponieważ tak trudno jest rzeczywiście popełnić ten błąd w.Net w porównaniu do innych języków, takich jak C, myślę, że ten problem zostanie przyćmiony przez inne rzeczy, takie jak wymienione powyżej. Powodzenia!

Oryginalna Odpowiedź Zombie może istnieć, jeśli nie napiszesz odpowiedniego kodu wątkowego. To samo dotyczy innych języków, takich jak C / C++ i Java. Ale nie jest to powód, aby nie pisać kodu w .NET.

I tak jak w każdym innym języku, poznaj cenę przed użyciem czegoś. Pomaga również wiedzieć, co dzieje się pod maską, dzięki czemu można przewidzieć wszelkie potencjalne problemy.

Niezawodny kod dla Systemów o znaczeniu krytycznym nie jest łatwy do napisania, niezależnie od języka, w którym się znajdujesz. Ale jestem pewien, że nie jest to niemożliwe zrobić poprawnie w. NET. również AFAIK,. NET threading nie różni się tak bardzo od threadingu w C / C++ używa (lub jest zbudowany z) tych samych wywołań systemowych, z wyjątkiem niektórych specyficznych dla. Net konstrukcji (takich jak lekkie wersje RWL i klasy event).

pierwszy raz słyszałem o pojęciu zombie, ale na podstawie Twojego opisu kolega pewnie miał na myśli wątek, który zakończył się bez Wydania wszystkich zasobów. Może to potencjalnie spowodować impas, wyciek pamięci lub inny zły efekt uboczny. To oczywiście nie jest pożądane, ale wyodrębnienie. NET z tego powodu Możliwość prawdopodobnie nie jest dobrym pomysłem, ponieważ jest to możliwe również w innych językach. Powiedziałbym nawet, że w C/C++ łatwiej jest namieszać niż w. NET (szczególnie w C gdzie nie ma RAII) , ale wiele krytycznych aplikacji jest napisanych w C/C++ prawda? Więc to naprawdę zależy od indywidualnych okoliczności. Jeśli chcesz wydobyć ze swojej aplikacji każdą uncję prędkości i chcesz zbliżyć się do gołego metalu, to.net może nie być najlepszym rozwiązaniem. Jeśli jesteś przy napiętym budżecie i wykonując wiele połączeń z usługami sieciowymi/istniejącymi bibliotekami. NET/etc,. NET może być dobrym wyborem.

 45
Author: Jerahmeel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-20 15:21:48

W tej chwili większość mojej odpowiedzi została poprawiona przez komentarze poniżej. Nie będę usuwał odpowiedzi , ponieważ potrzebuję punktów reputacji , ponieważ informacje w komentarzach mogą być cenne dla czytelników.

Immortal Blue zwrócił uwagę, że w. NET 2.0 i nowszych finally bloki są odporne na przerywanie wątków. Jak skomentował Andreas Niedermair, może to nie być prawdziwy wątek zombie, ale poniższy przykład pokazuje, jak przerwanie wątku może spowodować problemy:

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

Jednak przy użyciu bloku lock() { }, finally nadal będzie wykonywany, gdy ThreadAbortException zostanie wywołany w ten sposób.

Jak się okazuje, poniższe informacje są ważne tylko dla. NET 1 i. NET 1.1:

Jeśli wewnątrz bloku lock() { } pojawi się inny wyjątek, a ThreadAbortException pojawi się dokładnie wtedy, gdy blok finally ma być uruchomiony, blokada nie zostanie zwolniona. Jak wspomniałeś, blok lock() { } jest skompilowany jako:

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

Jeśli inny wątek wywoła Thread.Abort() wewnątrz wygenerowanego bloku finally, blokada może nie zostać zwolniona.

 25
Author: C.Evenhuis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-19 15:04:23

Tu nie chodzi o wątki Zombie, ale książka Effective C# ma sekcję dotyczącą implementacji IDisposable, (poz. 17), która mówi o obiektach Zombie, które moim zdaniem mogą Cię zainteresować.

Zalecam przeczytanie samej książki, ale jej sednem jest to, że jeśli masz klasę implementującą IDisposable lub zawierającą Descructor, jedyną rzeczą, którą powinieneś robić w obu przypadkach, jest zwolnienie zasobów. Jeśli robisz tu inne rzeczy, istnieje szansa, że obiekt nie będzie być zbierane śmieci, ale również nie będą dostępne w żaden sposób.

Daje przykład podobny do poniższego:

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

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

Kiedy Destruktor na tym obiekcie jest wywoływany, odniesienie do niego jest umieszczane na liście globalnej, co oznacza, że pozostaje żywe i w pamięci przez całe życie programu, ale nie jest dostępne. Może to oznaczać, że zasoby (szczególnie zasoby niezarządzane) mogą nie być w pełni uwolnione, co może powodować różnego rodzaju potencjalne problemy.

Pełniejszym przykładem jest poniżej. Do czasu osiągnięcia pętli foreach, na liście nieumarłych znajduje się 150 obiektów, z których każdy zawiera obraz, ale obraz został GC ' D i otrzymasz wyjątek, jeśli spróbujesz go użyć. W tym przykładzie otrzymuję ArgumentException (parametr jest nieprawidłowy), gdy próbuję zrobić cokolwiek z obrazem, czy próbuję go zapisać, czy nawet wyświetlić wymiary takie jak wysokość i szerokość:

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

Ponownie, wiem, że pytałeś o wątki zombie w szczególności, ale tytuł pytania jest o zombie w. Net, i przypomniało mi się o tym i pomyślałem, że inni mogą uznać to za interesujące!

 22
Author: JMK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-19 12:31:55

W krytycznych systemach pod dużym obciążeniem pisanie kodu bez blokady jest lepsze przede wszystkim ze względu na poprawę wydajności. Spójrz na rzeczy takie jak LMAX i jak wykorzystuje "mechaniczną sympatię" do wielkich dyskusji na ten temat. Martwisz się jednak o wątki zombie? Myślę, że to przypadek edge ' a, który jest tylko błędem do usunięcia, a nie wystarczająco dobrym powodem, aby nie używać lock.

Brzmi bardziej, jakby twój przyjaciel był po prostu fantazyjny i obnosił się ze swoją wiedzą o niejasnych egzotycznych terminologia dla mnie! Przez cały czas, gdy prowadziłem laboratoria wydajności w Microsoft Polska, nigdy nie natknąłem się na przypadek tego problemu w .NET.

 20
Author: James World,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-19 08:46:26

1.Is jest jaśniejsza definicja "wątku zombie" niż to, co wyjaśniłem tutaj?

Zgadzam się, że" wątki Zombie " Istnieją, jest to termin odnoszący się do tego, co dzieje się z wątkami, które pozostały z zasobami, których nie puszczają, a jednak nie umierają całkowicie, stąd nazwa "zombie", więc twoje wyjaśnienie tego skierowania jest całkiem dobre na pieniądze!

2.Czy wątki zombie mogą występować na. Net? (Dlaczego/dlaczego nie?)

Tak, mogą wystąpić. To jest w systemie Windows MSDN używa słowa "Zombie" dla martwych procesów / wątków

Dzieje się często to inna historia i zależy od twoich technik kodowania i praktyk, jeśli chodzi o Ciebie, że jak blokowanie wątków i zrobiłeś to przez jakiś czas, nie martwiłbym się nawet o ten scenariusz, który przydarzy się Tobie.

I tak, jak @ KevinPanko słusznie wspomniał w komentarzach, "wątki Zombie" pochodzą z Uniksa, dlatego są używane w XCode-objective i określany jako "NSZombie" i używany do debugowania. Zachowuje się prawie tak samo... jedyną różnicą jest to, że obiekt, który powinien umrzeć, staje się" ZombieObject "do debugowania zamiast" wątku Zombie", który może być potencjalnym problemem w Twoim kodzie.

 3
Author: Steven Hernandez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-19 22:04:23

Mogę łatwo tworzyć wątki zombie.

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

To przecieka uchwyty wątku (dla Join()). To kolejny wyciek pamięci, jeśli chodzi o zarządzany świat.

Teraz, zabicie nici w taki sposób, że faktycznie trzyma zamki, jest bólem z tyłu, ale możliwe. Drugi facet wykonuje swoją robotę. Jak stwierdził, Uchwyt Pliku został wyczyszczony przez gc, ale lock wokół obiektu nie. ale dlaczego to zrobiłeś?
 0
Author: Joshua,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-14 03:41:49