Jak garbage collector może uniknąć nieskończonej pętli?

101

Rozważmy następujący program w C #, przesłałem go na codegolf jako odpowiedź na utworzenie pętli bez zapętlania:

class P{
    static int x=0;
    ~P(){
        System.Console.WriteLine(++x);
        new P();
    }
    static void Main(){
        new P();
    }
}

Ten program wygląda jak nieskończona pętla podczas mojej inspekcji, ale wydaje się, że działa przez kilka tysięcy iteracji, a następnie program kończy się pomyślnie bez błędu (nie są zgłaszane żadne błędy). Czy jest to naruszenie specyfikacji, którego finalizator Postatecznie nie jest wywoływany?

Najwyraźniej jest to głupi kod, który nigdy nie powinien się pojawić, ale jestem ciekawy, jak program mógłby kiedykolwiek zakończyć się.

Oryginalny kod słupka golfowego :: /codegolf/33196/loop-without-looping/33218#33218

Michael B.
źródło
49
Boję się tego uruchomić.
Eric Scherrer
6
To, że finalizator nie jest wywoływany, z pewnością mieści się w sferze prawidłowego zachowania . Nie wiem jednak, dlaczego kłopotliwe jest uruchomienie kilku tysięcy iteracji, spodziewałbym się zerowych wywołań.
27
Środowisko CLR ma ochronę przed wątkiem finalizatora, który nigdy nie będzie w stanie zakończyć swojego zadania. Mocno kończy go po 2 sekundach.
Hans Passant
2
Tak więc prawdziwa odpowiedź na twoje pytanie w tytule jest taka, że ​​unika tego, pozwalając nieskończonej pętli działać przez 40 sekund, a następnie jest przerywana.
Lasse V. Karlsen
4
Po wypróbowaniu wydaje się, że program po prostu zabija wszystko po 2 sekundach bez względu na wszystko. Właściwie, jeśli będziesz nadal tworzyć wątki, potrwa to trochę dłużej :)
Michael B

Odpowiedzi:

110

Według Richtera w drugiej edycji CLR przez C # (tak, muszę zaktualizować):

Strona 478

Dla (CLR jest zamykany) każda metoda Finalize ma około dwóch sekund na powrót. Jeśli metoda Finalize nie zwróci w ciągu dwóch sekund, środowisko CLR po prostu zabija proces - nie są już wywoływane żadne metody Finalize . Ponadto, jeśli wywołanie metod Finalize wszystkich obiektów zajmie więcej niż 40 sekund , to ponownie CLR po prostu zabija proces.

Ponadto, jak wspomina Servy, ma swój własny wątek.

Eric Scherrer
źródło
5
Każda metoda finalizacji w tym kodzie zajmuje znacznie mniej niż 40 sekund na obiekt. To, że nowy obiekt jest tworzony, a następnie kwalifikuje się do finalizacji, nie ma znaczenia dla bieżącego finalizatora.
Jacob Krall,
2
Właściwie to nie jest to, co wykonuje swoją pracę. Występuje również limit czasu w wolnej kolejce opróżnianej przy zamykaniu. Na czym polega błąd tego kodu, wciąż dodaje nowe obiekty do tej kolejki.
Hans Passant
Samo myślenie o tym nie oznacza opróżniania kolejki wolnej, tak samo jak „Ponadto, jeśli wywołanie metod Finalize wszystkich obiektów zajmie więcej niż 40 sekund, to CLR po prostu zabija proces”.
Eric Scherrer
23

Finalizator nie działa w głównym wątku. Finalizator ma własny wątek, który uruchamia kod i nie jest to wątek pierwszego planu, który utrzymywałby działanie aplikacji. Główny wątek kończy się skutecznie od razu, w którym to momencie wątek finalizatora działa po prostu tyle razy, ile ma szansę, zanim proces zostanie zerwany. Nic nie utrzymuje programu przy życiu.

Servy
źródło
Jeśli finalizator nie zakończył działania po 40 sekundach od zakończenia programu, ponieważ żaden główny wątek nie jest aktywny, zostanie zakończony, a proces zostanie zakończony. Są to jednak stare wartości, więc Microsoft mógł do tej pory zmienić rzeczywiste liczby lub nawet cały algorytm. Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
Lasse V. KARLSEN
@ LasseV.Karlsen Czy to udokumentowane zachowanie języka, czy po prostu sposób, w jaki firma MS zdecydowała się zaimplementować swoje finalizatory jako szczegół implementacji? Spodziewałbym się tego drugiego.
Servy
Tego też oczekuję. Najbardziej oficjalnym odniesieniem do tego zachowania, jakie widziałem, jest to, co Eric powyżej zamieścił w swojej odpowiedzi, z książki CLR przez C # autorstwa Jeffrey'a Richtera.
Lasse V. Karlsen
8

Odśmiecacz nie jest aktywnym systemem. Działa „czasami” i głównie na żądanie (na przykład, gdy wszystkie strony oferowane przez system operacyjny są pełne).

Większość odśmiecaczy działa w wątku podobnym do pierwszej generacji. W większości przypadków może minąć kilka godzin, zanim obiekt zostanie poddany recyklingowi.

Jedyny problem występuje, gdy chcesz zakończyć program. Jednak to nie jest problem. Podczas korzystania killz systemu operacyjnego poprosimy grzecznie o zakończenie procesów. Kiedy jednak proces pozostaje aktywny, można użyć kill -9miejsca, w którym system operacyjny usuwa całą kontrolę.

Kiedy uruchomiłem Twój kod w csharpśrodowisku interaktywnym , otrzymałem:

csharp>  

1
2

Unhandled Exception:
System.NotSupportedException: Stream does not support writing
  at System.IO.FileStream.Write (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushBytes () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushCore () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] val) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.String val) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.Write (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.SynchronizedWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.Console.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at P.Finalize () [0x00000] in <filename unknown>:0

W ten sposób program ulega awarii, ponieważ stdoutjest blokowany przez zamknięcie środowiska.

Podczas usuwania Console.WriteLinei zabijania programu. Po pięciu sekundach program się kończy (innymi słowy, garbage collector poddaje się i po prostu zwalnia całą pamięć bez uwzględnienia finalizatorów).

Willem Van Onsem
źródło
To fascynujące, że interaktywny csharp wybucha z zupełnie innych powodów. Oryginalny fragment programu nie miał linii zapisu konsoli, jestem ciekawy, czy też by się zakończył.
Michael B,
@MichaelB: Też to przetestowałem (patrz komentarz poniżej). Czeka przez pięć sekund, a następnie kończy. Myślę, że finalizator pierwszej Pinstancji po prostu przekroczył limit czasu.
Willem Van Onsem