Come fa un garbage collector a evitare un ciclo infinito qui?


101

Considera il seguente programma C #, l'ho inviato su codegolf come risposta per creare un loop senza loop:

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

Questo programma sembra un ciclo infinito nella mia ispezione, ma sembra funzionare per diverse migliaia di iterazioni, quindi il programma termina correttamente senza errori (non vengono generati errori). È una violazione delle specifiche che alla Pfine il finalizzatore non viene chiamato?

Chiaramente questo è un codice stupido, che non dovrebbe mai apparire, ma sono curioso di sapere come il programma potrebbe mai completarsi.

Codice originale golf post: /codegolf/33196/loop-without-looping/33218#33218


49
Ho paura di farlo funzionare.
Eric Scherrer

6
Il fatto che un finalizzatore non venga chiamato è certamente nell'ambito del comportamento valido . Non so perché sia ​​fastidioso eseguire diverse migliaia di iterazioni, mi aspetto zero invocazioni.

27
CLR è protetto contro il thread del finalizzatore che non è mai in grado di completare il proprio lavoro. Lo termina forzatamente dopo 2 secondi.
Hans Passant

2
Quindi la vera risposta alla tua domanda nel titolo è che lo evita lasciando che il ciclo infinito venga eseguito per 40 secondi e poi venga terminato.
Lasse V. Karlsen

4
Dopo averlo provato, sembra che il programma uccida tutto dopo 2 secondi, non importa quale. In realtà, se continui a generare thread, durerà un po 'più a lungo :)
Michael B

Risposte:


110

Come per Richter nella seconda edizione di CLR tramite C # (sì, ho bisogno di aggiornare):

Pagina 478

Per (Il CLR si sta spegnendo) ogni metodo Finalize ha circa due secondi per tornare. Se un metodo Finalize non viene restituito entro due secondi, CLR interrompe semplicemente il processo e non vengono più chiamati metodi Finalize . Inoltre, se sono necessari più di 40 secondi per chiamare i metodi Finalize di tutti gli oggetti , di nuovo, il CLR uccide semplicemente il processo.

Inoltre, come menziona Servy, ha il suo filo conduttore.


5
Ciascun metodo finalize in questo codice richiede meno di 40 secondi per oggetto. Il fatto che un nuovo oggetto venga creato e quindi idoneo per la finalizzazione non è rilevante per il finalizzatore corrente.
Jacob Krall

2
Questo non è effettivamente ciò che fa il lavoro. C'è anche un timeout sulla coda freachable che viene svuotata allo spegnimento. Che è ciò su cui questo codice fallisce, continua ad aggiungere nuovi oggetti a quella coda.
Hans Passant

Solo a pensarci, svuotare la coda freachable non è lo stesso di "Inoltre, se ci vogliono più di 40 secondi per chiamare i metodi Finalize di tutti gli oggetti, di nuovo, il CLR uccide semplicemente il processo."
Eric Scherrer

23

Il finalizzatore non viene eseguito nel thread principale. Il finalizzatore ha un proprio thread che esegue il codice e non è un thread in primo piano che manterrebbe in esecuzione l'applicazione. Il thread principale viene completato immediatamente in modo efficace, a quel punto il thread del finalizzatore viene eseguito tutte le volte che ne ha la possibilità prima che il processo venga interrotto. Niente mantiene in vita il programma.


Se il finalizzatore non è stato completato 40 secondi dopo che il programma sarebbe dovuto essere chiuso a causa dell'assenza di thread principali attivi, verrà terminato e il processo terminerà. Questi sono valori vecchi, quindi Microsoft potrebbe aver modificato i numeri effettivi o anche l'intero algoritmo ormai. Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
Lasse V. Karlsen

@ LasseV.Karlsen Questo è il comportamento documentato della lingua o semplicemente il modo in cui MS ha scelto di implementare i finalizzatori come dettaglio di implementazione? Mi aspetto il secondo.
Servy

Mi aspetto anche quest'ultimo. Il riferimento più ufficiale a questo comportamento che ho visto è quello che Eric ha pubblicato sopra nella sua risposta, dal libro CLR via C # di Jeffrey Richter.
Lasse V. Karlsen

8

Un garbage collector non è un sistema attivo. Funziona "a volte" e per lo più su richiesta (ad esempio quando tutte le pagine offerte dal sistema operativo sono piene).

La maggior parte dei garbage collector funziona in un modo simile alla prima generazione in un sotto-thread. Nella maggior parte dei casi possono essere necessarie ore prima che l'oggetto venga riciclato.

L'unico problema si verifica quando si desidera terminare il programma. Tuttavia non è davvero un problema. Quando si utilizza killun sistema operativo chiederà cortesemente di terminare i processi. Quando il processo rimane comunque attivo, si può usare kill -9dove il sistema operativo rimuove tutto il controllo.

Quando ho eseguito il tuo codice nell'ambiente interattivo csharp, ho ottenuto:

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

Quindi il tuo programma va in crash perché stdoutè bloccato dalla terminazione dell'ambiente.

Quando si rimuove Console.WriteLinee si uccide il programma. Dopo cinque secondi il programma termina (in altre parole, il garbage collector si arrende e semplicemente libererà tutta la memoria senza prendere in considerazione i finalizzatori).


È affascinante che il csharp interattivo esploda per ragioni completamente diverse. Lo snippet del programma originale non aveva la riga di scrittura della console, sono curioso di sapere se anche questo sarebbe terminato.
Michael B

@MichaelB: ho provato anche questo (vedi commento sotto). Attende cinque secondi e poi termina. Immagino che il finalizzatore della prima Pistanza sia semplicemente scaduto.
Willem Van Onsem
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.