Czy zombie istnieją… w .NET?

385

Rozmawiałem z kolegą z zespołu na temat blokowania w .NET. To naprawdę bystry facet z dużym doświadczeniem zarówno w programowaniu niższego, jak i wyższego poziomu, ale jego doświadczenie w programowaniu na niższym poziomie znacznie przewyższa moje. W każdym razie argumentował, że należy unikać blokowania platformy .NET w krytycznych systemach, które powinny być obciążone, o ile to w ogóle możliwe, aby uniknąć dopuszczalnej niewielkiej możliwości awarii wątku „zombie”. Rutynowo używam blokowania i nie wiedziałem, co to jest „nić zombie”, więc zapytałem. Mam wrażenie, że z jego wyjaśnienia wynika, że ​​nić zombie to nić, która się zakończyła, ale jakoś wciąż trzyma pewne zasoby. Podał przykład, w jaki sposób nić zombie może złamać system, gdy nić rozpoczyna procedurę po zablokowaniu jakiegoś obiektu, a następnie jest w pewnym momencie zakończone przed zwolnieniem blokady. Ta sytuacja może spowodować awarię systemu, ponieważ w końcu próby wykonania tej metody spowodują, że wątki będą oczekiwać na dostęp do obiektu, który nigdy nie zostanie zwrócony, ponieważ wątek korzystający z zablokowanego obiektu jest martwy.

Myślę, że mam tego sedno, ale jeśli jestem poza bazą, daj mi znać. Ta koncepcja miała dla mnie sens. Nie byłem do końca przekonany, że to był prawdziwy scenariusz, który mógłby się wydarzyć w .NET. Nigdy wcześniej nie słyszałem o „zombie”, ale zdaję sobie sprawę, że programiści, którzy pracowali dogłębnie na niższych poziomach, zwykle lepiej rozumieją podstawy obliczeń (takie jak wątki). Zdecydowanie widzę jednak wartość blokowania i widziałem wielu światowej klasy programistów korzystających z blokowania. Mam również ograniczoną zdolność do oceny tego dla siebie, ponieważ wiem, że lock(obj)stwierdzenie to tak naprawdę tylko cukier składniowy dla:

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

i ponieważ Monitor.Enteri Monitor.Exitsą oznaczone extern. Wydaje się możliwe, że .NET wykonuje pewien rodzaj przetwarzania, który chroni wątki przed narażeniem na komponenty systemu, które mogłyby mieć taki wpływ, ale jest to czysto spekulacyjne i prawdopodobnie oparte na fakcie, że nigdy nie słyszałem o „wątkach zombie” przed. Mam nadzieję, że uda mi się uzyskać informacje zwrotne na ten temat:

  1. Czy istnieje wyraźniejsza definicja „nici zombie” niż to, co tutaj wyjaśniłem?
  2. Czy wątki zombie mogą występować w .NET? (Dlaczego? Dlaczego nie?)
  3. Jeśli dotyczy, jak mogę wymusić utworzenie nici zombie w .NET?
  4. Jeśli dotyczy, jak mogę wykorzystać blokowanie bez ryzyka scenariusza wątku zombie w .NET?

Aktualizacja

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

Obiekt jest w stanie zombie.

smartcaveman
źródło
8
czy jesteś pewien, że twój partner nie mówi o impasie?
Andreas Niedermair,
10
@AndreasNiedermair - Wiem, co to jest impas i najwyraźniej nie chodziło o niewłaściwe użycie tej terminologii. W rozmowie wspomniano o zakleszczeniu i wyraźnie różniło się ono od „nici zombie”. Dla mnie główne rozróżnienie polega na tym, że martwy zamek ma dwukierunkową nierozstrzygalną zależność, podczas gdy wątek zombie jest jednokierunkowy i wymaga przerwanego procesu. Jeśli nie zgadzasz się i uważasz, że jest lepszy sposób, aby spojrzeć na te rzeczy, wyjaśnij
smartcaveman,
19
Myślę, że termin „zombie” faktycznie pochodzi od tła UNIX, tak jak w „procesie zombie”, prawda? W UNIX istnieje jasna definicja „procesu zombie”: opisuje on proces potomny, który został zakończony, ale w którym rodzic procesu potomnego nadal musi „zwolnić” proces potomny (i jego zasoby) przez wywołanie waitlub waitpid. Proces potomny jest następnie nazywany „procesem zombie”. Zobacz także howtogeek.com/119815
hogliux,
9
Jeśli część programu ulegnie awarii, pozostawiając program w nieokreślonym stanie, może to oczywiście powodować problemy z resztą programu. To samo może się zdarzyć, jeśli niepoprawnie obsługujesz wyjątki w programie jednowątkowym. Problem nie dotyczy wątków, problem polega na tym, że masz globalny stan zmiennych i że nie radzisz sobie z nieoczekiwanym zakończeniem wątku. Twój „naprawdę bystry” współpracownik jest całkowicie nie na miejscu.
Jim Mischel,
9
„Od czasu pierwszych komputerów w maszynie zawsze znajdowały się duchy. Losowe segmenty kodu, które zgrupowały się, tworząc nieoczekiwane protokoły ...”
Chris Laplante,

Odpowiedzi:

242
  • Czy istnieje wyraźniejsza definicja „nici zombie” niż to, co tutaj wyjaśniłem?

Wydaje mi się to całkiem dobrym wytłumaczeniem - wątek, który zakończył się (i dlatego nie może już zwolnić żadnych zasobów), ale którego zasoby (np. Uchwyty) są nadal dostępne i (potencjalnie) powodują problemy.

  • Czy wątki zombie mogą występować w .NET? (Dlaczego? Dlaczego nie?)
  • Jeśli dotyczy, jak mogę wymusić utworzenie nici zombie w .NET?

Z pewnością tak, słuchaj, 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, Targetktóry otwiera plik, a następnie natychmiast się zabija ExitThread. Wynikowy wątek zombie nigdy nie zwolni uchwytu do pliku „test.txt”, więc plik pozostanie otwarty do momentu zakończenia programu (możesz to sprawdzić za pomocą eksploratora procesów lub podobnego narzędzia). Uchwyt do „test.txt” nie zostanie zwolniony, dopóki nie GC.Collectzostanie wywołany - okazuje się, że trudniej jest niż stworzyć wątek zombie, który przecieka uchwyty)

  • Jeśli dotyczy, jak mogę wykorzystać blokowanie bez ryzyka scenariusza wątku zombie w .NET?

Nie rób tego, co właśnie zrobiłem!

Tak długo, jak twój kod czyści się poprawnie po sobie (użyj Bezpiecznych uchwytów lub równoważnych klas, jeśli pracujesz z niezarządzanymi zasobami) i dopóki nie robisz nic, aby zabijać wątki w dziwny i cudowny sposób (najbezpieczniejszy jest po prostu aby nigdy nie zabijać wątków - pozwól im zakończyć się normalnie lub, jeśli to konieczne, poprzez wyjątki), jedynym sposobem na uzyskanie czegoś podobnego do wątku zombie jest, jeśli coś poszło bardzo źle (np. coś poszło nie tak w CLR).

W rzeczywistości zaskakująco trudno jest stworzyć wątek zombie (musiałem P / Invoke w funkcji, która w istocie mówi ci w dokumentacji, aby nie wywoływać go poza C). Na przykład poniższy (okropny) kod w rzeczywistości nie tworzy nici 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 bardzo okropnych błędów uchwyt „test.txt” jest zamykany natychmiast po Abortwywołaniu (jako część finalizatora, dla filektórego pod okładkami używa SafeFileHandle do zawijania swojego uchwytu pliku)

Przykład blokowania w odpowiedzi C.Evenhuis jest prawdopodobnie najłatwiejszym sposobem, aby nie zwolnić zasobu (w tym przypadku blokady), gdy wątek zostanie zakończony w nietypowy sposób, ale można to łatwo naprawić za pomocą lockinstrukcji lub umieszczenie wydania w finallybloku.

Zobacz też

Justin
źródło
3
pamiętam, kiedy grałem w zapisywanie rzeczy w programie Excel przy użyciu narzędzia do pracy w tle, nie zwalniałem wszystkich zasobów przez cały czas (ponieważ właśnie pominąłem debugowanie itp.). w menedżerze zadań zobaczyłem później około 50 procesów programu Excel. czy stworzyłem procesy zombieexcelprocesses?
Stefan,
3
@Justin - +1 - Świetna odpowiedź. Jestem jednak trochę sceptyczny wobec twojego ExitThreadtelefonu. Oczywiście działa, ale wydaje się bardziej sprytną sztuczką niż realistycznym scenariuszem. Jednym z moich celów jest nauczenie się, czego nie robić, aby przypadkowo nie tworzyć wątków zombie za pomocą kodu .NET. Prawdopodobnie mogłem zorientować się, że wywołanie kodu C ++, o którym wiadomo, że powoduje ten problem z kodu .NET, dałoby pożądany efekt. Oczywiście dużo wiesz o tych rzeczach. Czy znasz inne przypadki (być może dziwne, ale niewystarczająco dziwne, aby nigdy nie nastąpiły przypadkowo) z takim samym skutkiem?
smartcaveman,
3
Więc odpowiedź brzmi „nie w c # 4”, prawda? Jeśli musisz wyskoczyć z CLR, aby uzyskać wątek zombie, nie wygląda to na problem .Net.
Gusdor,
4
@Stefan Powiedziałbym, że prawie na pewno nie. Robiłem to, co opisałeś wiele razy. Procesy programu Excel nie są zombie, ponieważ nadal działają, choć nie są natychmiast dostępne za pośrednictwem zwykłego interfejsu użytkownika. Powinieneś być w stanie je odzyskać poprzez połączenia z GetObject . Set excelInstance = GetObject(, "Excel.Application")
Daniel,
7
„Wynikowy wątek zombie nigdy nie zwolni uchwytu do pliku„ test.txt ”, więc plik pozostanie otwarty, dopóki program się nie zakończy” jest nieprawidłowy. Mały dowód: `static void Main (string [] args) {new Thread (GcCollect) .Start (); nowy wątek (cel) .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

Trochę posprzątałem swoją odpowiedź, ale zostawiłem oryginalną poniżej w celach informacyjnych

Po raz pierwszy słyszę o zombie, więc przyjmuję, że jego definicja brzmi:

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

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

Jednak nie uważam tego za dobry powód, aby nie pisać wątkowego, krytycznego dla misji kodu w .NET. Mogą istnieć inne powody, aby zdecydować się na .NET, ale odpisanie .NET tylko dlatego, że możesz mieć wątki zombie, jakoś nie ma dla mnie sensu. Wątki zombie są możliwe w C / C ++ (nawet twierdzę, że dużo łatwiej jest popsuć się w C), a wiele krytycznych, wątkowych aplikacji znajduje się w C / C ++ (handel wysokonakładowy, bazy danych itp.).

Wniosek Jeśli decydujesz się na wybór języka, sugeruję, aby wziąć pod uwagę duży obraz: wydajność, umiejętności pracy w zespole, harmonogram, integrację z istniejącymi aplikacjami itp. Jasne, wątki zombie są czymś, o czym powinieneś pomyśleć , ale ponieważ tak trudno jest popełnić ten błąd w .NET w porównaniu do innych języków, takich jak C, myślę, że ta obawa zostanie przyćmiona przez inne rzeczy, takie jak te wymienione powyżej. Powodzenia!

Oryginalna odpowiedź Zombie mogą istnieć, jeśli nie napiszesz właściwego kodu wątków. To samo dotyczy innych języków, takich jak C / C ++ i Java. Ale to nie jest powód, aby nie pisać kodu wątkowego 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.

Wiarygodny kod dla systemów o kluczowym znaczeniu nie jest łatwy do napisania, niezależnie od języka, w jakim się znajdujesz. Jestem jednak pewien, że poprawne wykonanie w .NET nie jest niemożliwe. Również wątki AFAIK, .NET nie różnią się tak bardzo od wątków w C / C ++, używają (lub są zbudowane z) tych samych wywołań systemowych, z wyjątkiem niektórych specyficznych konstrukcji .net (takich jak lekkie wersje RWL i klasy zdarzeń).

Pierwszy raz słyszałem o zombie, ale na podstawie twojego opisu prawdopodobnie twój kolega oznaczał wątek, który zakończył się bez uwolnienia wszystkich zasobów. Może to potencjalnie spowodować impas, wyciek pamięci lub inny zły efekt uboczny. Nie jest to oczywiście pożądane, ale wyróżnienie platformy .NET z powodu tej możliwości prawdopodobnie nie jest dobrym pomysłem, ponieważ jest możliwe także w innych językach. Twierdziłbym nawet, że łatwiej jest zepsuć się w C / C ++ niż w .NET (szczególnie w C, gdzie nie masz RAII), ale wiele krytycznych aplikacji jest napisanych w C / C ++, prawda? Tak więc to naprawdę zależy od twoich indywidualnych okoliczności. Jeśli chcesz wyodrębnić każdą uncję prędkości z aplikacji i chcesz zbliżyć się do gołego metalu, jak to możliwe, to .NET możenie być najlepszym rozwiązaniem. Jeśli masz napięty budżet i dużo współpracujesz z usługami internetowymi / istniejącymi bibliotekami .net / etc, to .NET może być dobrym wyborem.

Jerahmeel
źródło
(1) Nie jestem pewien, jak dowiedzieć się, co dzieje się pod maską, gdy uderzę w ślepe externzaułki. Jeśli masz sugestię, chętnie ją usłyszę. (2) Zgadzam się, że można to zrobić w .NET. Chciałbym wierzyć, że jest to możliwe przy blokowaniu, ale jeszcze nie znalazłem satysfakcjonującej odpowiedzi, aby uzasadnić to dzisiaj
smartcaveman
1
@smartcaveman jeśli co masz na myśli przez lockingto lockprawdopodobnie nie słów kluczowych, a następnie ponieważ serializes wykonanie. Aby zmaksymalizować przepustowość, musisz użyć odpowiednich konstrukcji w zależności od właściwości kodu. Powiedziałbym, że jeśli możesz napisać niezawodny kod w c / c ++ / java / cokolwiek przy użyciu pthreads / boost / thread pool / cokolwiek, to możesz również napisać go w języku C #. Ale jeśli nie możesz napisać wiarygodnego kodu w dowolnym języku przy użyciu dowolnej biblioteki, to wątpię, aby pisanie w C # wyglądało inaczej.
Jerahmeel,
@smartcaveman, aby dowiedzieć się, co kryje się pod maską, Google pomaga w stosach, a jeśli problem jest zbyt egzotyczny, aby znaleźć go w Internecie, reflektor jest bardzo przydatny. Jednak w przypadku klas wątków uważam, że dokumentacja MSDN jest bardzo przydatna. Wiele z nich to tylko opakowania dla tych samych wywołań systemowych, których używasz w C.
Jerahmeel,
1
@smartcaveman wcale nie, nie o to mi chodziło. Przepraszam, jeśli tak się stało. Chciałem powiedzieć, że nie powinieneś zbyt szybko odpisywać .NET podczas pisania krytycznej aplikacji. Pewnie, możesz robić rzeczy, które mogą być znacznie gorsze niż wątki zombie (które moim zdaniem są tylko wątkami, które nie uwolniły żadnych niezarządzanych zasobów, co może się zdarzyć w innych językach: stackoverflow.com/questions/14268080/... ), ale znowu, to nie znaczy, że .NET nie jest realnym rozwiązaniem.
Jerahmeel,
2
Nie sądzę, że .NET to w ogóle zła technologia. To właściwie moja podstawowa platforma programistyczna. Ale z tego powodu uważam, że ważne jest zrozumienie wad, na które jest podatny. Zasadniczo wyróżniam platformę .NET, ale dlatego, że mi się podoba, a nie dlatego, że jej nie lubię. (I nie martw się, stary, ja + 1-ed ciebie)
smartcaveman
26

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

Immortal Blue wskazał, że w .NET 2.0 i nowszych finallyblokach są odporne na przerywanie wątków. I jak skomentował Andreas Niedermair, może to nie być rzeczywisty wątek zombie, ale poniższy przykład pokazuje, jak przerwanie wątku może powodować 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 w przypadku użycia lock() { }bloku finallynadal byłby wykonywany, gdy ThreadAbortExceptionzostanie w ten sposób wystrzelony.

Okazuje się, że następujące informacje dotyczą tylko .NET 1 i .NET 1.1:

Jeśli wewnątrz lock() { }bloku wystąpi inny wyjątek, który ThreadAbortExceptiondotrze dokładnie wtedy, gdy finallyblok ma zostać uruchomiony, zamek nie zostanie zwolniony. Jak wspomniałeś, lock() { }blok jest kompilowany jako:

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

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

C.Evenhuis
źródło
2
mówisz, lock()ale nie widzę żadnego użycia tego ... więc - jak to się łączy? jest to „niewłaściwe” użycie Monitor.Enteri Monitor.Exit(brak użycia tryi finally)
Andreas Niedermair
4
Nie nazwałbym tego wątkiem zombie - jest to po prostu niewłaściwe użycie Monitor.Enteri Monitor.Exitbez właściwego użycia tryi finally- w każdym razie twój scenariusz zablokuje inne wątki, które mogą się zawiesić _lock, więc jest scenariusz impasu - niekoniecznie wątek zombie ... Poza tym nie zwalniasz zamka w Main... ale, hej ... może OP blokuje się w celu zakleszczenia zamiast wątków zombie :)
Andreas Niedermair
1
@AndreasNiedermair być może definicja nici zombie nie jest tym, co myślałem. Być może możemy nazwać to „wątkiem, który zakończył wykonywanie, ale nie zwolnił wszystkich zasobów”. Kusiło mnie, aby usunąć i odrobić pracę domową, ale zachowując odpowiedź na podobieństwo do scenariusza PO.
C.Evenhuis,
2
bez obrazy tutaj! właściwie twoja odpowiedź sprawiła, że ​​pomyślałem o tym :) ponieważ OP wyraźnie mówi o blokadach, które nie zostały zwolnione, wierzę, że jego partner mówił o martwym blokowaniu - ponieważ blokada nie jest prawdziwym zasobem, który jest powiązany z wątkiem jest udostępniony ... nöna) - i dlatego można uniknąć każdego wątku „zombie”, stosując się do najlepszych praktyk i używając locklub właściwego try/ finally-użytkowania
Andreas Niedermair,
1
@ C.Evenhuis - Nie jestem pewien, czy moja definicja jest dokładna. Częściowo dlatego zadaję pytanie, aby to wyjaśnić. Myślę, że pojęcie to jest bardzo mocno wspomniane w C / C ++
smartcaveman,
24

Nie chodzi o nici Zombie, ale książka Effective C # zawiera sekcję dotyczącą implementacji IDisposable (pozycja 17), która mówi o obiektach Zombie, które moim zdaniem mogą być interesujące.

Zalecam przeczytanie samej książki, ale jej sedno polega na tym, że jeśli masz klasę implementującą IDisposable lub zawierającą desctructor, jedyną rzeczą, którą powinieneś zrobić w obu przypadkach, jest uwolnienie zasobów. Jeśli zrobisz tutaj inne rzeczy, istnieje szansa, że ​​obiekt nie zostanie wyrzucony, ale również nie będzie w żaden sposób dostępny.

Podaje przykład podobny do poniższego:

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

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

Po wywołaniu destruktora tego obiektu na globalnej liście umieszczane jest odwołanie do samego siebie, co oznacza, że ​​pozostaje on żywy i pozostaje w pamięci przez cały czas działania programu, ale nie jest dostępny. Może to oznaczać, że zasoby (szczególnie zasoby niezarządzane) mogą nie zostać w pełni zwolnione, co może powodować różnego rodzaju potencjalne problemy.

Bardziej kompletny przykład znajduje się poniżej. Do momentu osiągnięcia pętli foreach masz na liście Nieumarłych 150 obiektów, z których każdy zawiera obraz, ale obraz został poddany GC i otrzymujesz wyjątek, jeśli spróbujesz go użyć. W tym przykładzie otrzymuję ArgumentException (parametr nie jest prawidłowy), gdy próbuję zrobić cokolwiek z obrazem, niezależnie od tego, 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);
    }
}

Znów wiem, że pytasz w szczególności o wątki zombie, ale tytuł pytania dotyczy zombie w .net, przypomniałem sobie o tym i pomyślałem, że inni mogą uznać to za interesujące!

JMK
źródło
1
To jest interesujące. Czy _undeadma być statyczny?
smartcaveman,
1
Próbowałem więc, drukując „zniszczony” obiekt. Traktuje to jak zwykły przedmiot. Czy jest w tym coś problematycznego?
smartcaveman,
1
Zaktualizowałem swoją odpowiedź przykładem, który, mam nadzieję, nieco bardziej pokazuje problem.
JMK
1
Nie wiem, dlaczego zostałeś odrzucony. Uznałem to za pomocne. Oto pytanie - jaki wyjątek dostaniesz? Nie spodziewam się, że tak NullReferenceException, ponieważ mam wrażenie, że brakująca rzecz musi być bardziej związana z maszyną niż z aplikacją. Czy to jest poprawne?
smartcaveman,
4
Z pewnością nie IDisposableo to chodzi. Mam problem z finalizatorami (destruktorami), ale sam fakt, IDisposableże obiekt nie trafi do kolejki finalizatora i nie zaryzykuje tego scenariusza zombie. To ostrzeżenie dotyczy finalizatorów i mogą oni wywoływać metody Dispose. Istnieją przykłady IDisposableużycia w typach bez finalizatorów. Nastrój powinien dotyczyć oczyszczania zasobów, ale może to być niebanalne czyszczenie zasobów. RX poprawnie wykorzystuje IDisposabledo czyszczenia subskrypcji i może wywoływać inne zasoby niższego rzędu. (Nie głosowałem też za btw ...)
James World
21

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 wspaniałych dyskusji na ten temat. Martwisz się o nici zombie? Myślę, że jest to przypadek, który jest tylko błędem do usunięcia i nie jest wystarczającym powodem, aby go nie używać lock.

Wygląda na to, że twój przyjaciel jest po prostu fantazyjny i obnosi się ze swoją wiedzą na temat niejasnej, egzotycznej terminologii! Przez cały czas, gdy prowadziłem laboratoria wydajności w Microsoft UK, nigdy nie natknąłem się na wystąpienie tego problemu w .NET.

James World
źródło
2
Myślę, że różne zestawy doświadczeń powodują, że paranoimy się na temat różnych błędów. Podczas wyjaśniania potwierdził, że jest to skrajny przypadek skrajny, ale jeśli naprawdę jest to przypadek skrajny, a nie przypadek, chciałbym przynajmniej lepiej to zrozumieć - Dziękuję za Twój wkład
smartcaveman
1
Słusznie. Po prostu nie chciałbym, abyś martwił się nieproporcjonalnie o to lockoświadczenie!
James World
1
Byłbym znacznie mniej zmartwiony, gdybym lepiej zrozumiał ten problem.
smartcaveman,
4
Głosowałem za tym, ponieważ próbujesz pozbyć się wątku FUD, którego jest o wiele za dużo. Lock-FUD zajmie więcej pracy, aby się go pozbyć :) Po prostu kulę się, gdy deweloperzy biorą nieograniczoną blokadę (dla wydajności), aby mogli skopiować 50 KB danych do szerokiej kolejki.
Martin James
3
Tak, zarówno ogólne, jak i szczegółowe. Pisanie poprawnego kodu bez blokady jest tak samo trudne jak programowanie. Z mojego doświadczenia wynika, że ​​w większości przypadków duże, gruboziarniste (względnie) łatwe do zrozumienia lockbloki są w porządku. Unikałbym wprowadzania złożoności w celu optymalizacji wydajności, dopóki nie będziesz wiedział, że musisz.
James World
3

1. Czy istnieje wyraźniejsza definicja „nici zombie” niż to, co tutaj wyjaśniłem?

Zgadzam się, że istnieją „Wątki Zombie”, jest to termin odnoszący się do tego, co dzieje się z Wątkami, które pozostają z zasobami, których nie wypuszczają, a jednak nie umierają całkowicie, stąd nazwa „zombie”, więc twój wyjaśnienie tego polecenia jest całkiem trafne w kwestii pieniędzy!

2.Czy wątki zombie występują w .NET? (Dlaczego? Dlaczego nie?)

Tak, mogą wystąpić. Jest to odniesienie i faktycznie określane przez Windows jako „zombie”: MSDN używa słowa „Zombie” dla Dead procesów / wątków

Często zdarza się, że jest to inna historia i zależy od twoich technik kodowania i praktyk, ponieważ dla ciebie, który lubi Blokowanie wątków i robił to przez jakiś czas, nie martwiłbym się nawet tym scenariuszem.

I tak, jak poprawnie wspomniano w komentarzach @KevinPanko, „Wątki Zombie” pochodzą z Uniksa, dlatego są używane w XCode-ObjectiveC i nazywane „NSZombie” i używane do debugowania. Zachowuje się prawie w ten sam sposób ... jedyną różnicą jest to, że obiekt, który powinien był umrzeć, staje się „obiektem ZombieObject” do debugowania zamiast „wątku Zombie”, co może być potencjalnym problemem w kodzie.

SH
źródło
1
Ale sposób, w jaki MSDN używa zombie, bardzo różni się od sposobu, w jaki używa tego pytania.
Ben Voigt,
1
O tak, zgadzam się, ale mówiłem, że nawet MSDN odnosi się do wątków jako Nici Zombie, gdy są martwe. i że w rzeczywistości mogą wystąpić.
SH
4
Jasne, ale odnosi się to do jakiegoś innego kodu, który wciąż trzyma uchwyt wątku, a nie wątku trzymającego uchwyty do zasobów, kiedy wychodził. Twoje pierwsze zdanie, zgodne z definicją zawartą w pytaniu, jest błędne.
Ben Voigt,
1
Ach, rozumiem o co ci chodzi. Szczerze mówiąc nawet tego nie zauważyłem. Skupiłem się na jego punkcie, że definicja istnieje, niezależnie od tego, jak to się dzieje. Pamiętaj, że definicja istnieje z powodu tego, co dzieje się z wątkiem, a nie tego, jak się to robi.
SH
0

Mogę łatwo tworzyć nici zombie.

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

To powoduje przeciekanie uchwytów nici (dla Join()). To tylko kolejny wyciek pamięci w zarządzanym świecie.

Teraz zabicie nici w sposób, w jaki faktycznie trzyma zamki, jest bólem z tyłu, ale jest możliwe. Drugi facet ExitThread()wykonuje robotę. Jak stwierdził, uchwyt pliku został wyczyszczony przez gc, ale lockwokół obiektu nie. Ale dlaczego miałbyś to zrobić?

Jozuego
źródło