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.Enter
i Monitor.Exit
są 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:
- Czy istnieje wyraźniejsza definicja „nici zombie” niż to, co tutaj wyjaśniłem?
- Czy wątki zombie mogą występować w .NET? (Dlaczego? Dlaczego nie?)
- Jeśli dotyczy, jak mogę wymusić utworzenie nici zombie w .NET?
- 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:
źródło
wait
lubwaitpid
. Proces potomny jest następnie nazywany „procesem zombie”. Zobacz także howtogeek.com/119815Odpowiedzi:
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.
Z pewnością tak, słuchaj, zrobiłem jeden!
Ten program uruchamia wątek,
Target
który otwiera plik, a następnie natychmiast się zabijaExitThread
.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 nieGC.Collect
zostanie wywołany - okazuje się, że trudniej jest niż stworzyć wątek zombie, który przecieka uchwyty)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.
Pomimo popełniania bardzo okropnych błędów uchwyt „test.txt” jest zamykany natychmiast po
Abort
wywołaniu (jako część finalizatora, dlafile
któ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ą
lock
instrukcji lub umieszczenie wydania wfinally
bloku.Zobacz też
lock
słowa kluczowego (ale tylko w .Net 3.5 i wcześniejszych)źródło
ExitThread
telefonu. 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?Set excelInstance = GetObject(, "Excel.Application")
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.
źródło
extern
zauł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 dzisiajlocking
tolock
prawdopodobnie 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.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
finally
blokach 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:Jednak w przypadku użycia
lock() { }
blokufinally
nadal byłby wykonywany, gdyThreadAbortException
zostanie 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óryThreadAbortException
dotrze dokładnie wtedy, gdyfinally
blok ma zostać uruchomiony, zamek nie zostanie zwolniony. Jak wspomniałeś,lock() { }
blok jest kompilowany jako:Jeśli inny wątek wywoła
Thread.Abort()
wewnątrz wygenerowanegofinally
bloku, blokada może nie zostać zwolniona.źródło
lock()
ale nie widzę żadnego użycia tego ... więc - jak to się łączy? jest to „niewłaściwe” użycieMonitor.Enter
iMonitor.Exit
(brak użyciatry
ifinally
)Monitor.Enter
iMonitor.Exit
bez właściwego użyciatry
ifinally
- 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 wMain
... ale, hej ... może OP blokuje się w celu zakleszczenia zamiast wątków zombie :)lock
lub właściwegotry
/finally
-użytkowaniaNie 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:
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ść:
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!
źródło
_undead
ma być statyczny?NullReferenceException
, ponieważ mam wrażenie, że brakująca rzecz musi być bardziej związana z maszyną niż z aplikacją. Czy to jest poprawne?IDisposable
o 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ładyIDisposable
użycia w typach bez finalizatorów. Nastrój powinien dotyczyć oczyszczania zasobów, ale może to być niebanalne czyszczenie zasobów. RX poprawnie wykorzystujeIDisposable
do czyszczenia subskrypcji i może wywoływać inne zasoby niższego rzędu. (Nie głosowałem też za btw ...)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.
źródło
lock
oświadczenie!lock
bloki są w porządku. Unikałbym wprowadzania złożoności w celu optymalizacji wydajności, dopóki nie będziesz wiedział, że musisz.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!
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.
źródło
Mogę łatwo tworzyć nici zombie.
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, alelock
wokół obiektu nie. Ale dlaczego miałbyś to zrobić?źródło