2012-03-03 7 views
29

Ho un server di lunga durata scritto in Go. Principali fuochi di diverse goroutine in cui viene eseguita la logica del programma. Dopo quello principale non fa nulla di utile. Una volta uscite principali, il programma si chiuderà. Il metodo che sto usando in questo momento per mantenere il programma in esecuzione è solo una semplice chiamata a fmt.Scanln(). Mi piacerebbe sapere come gli altri impediscono che l'essenziale esca. Di seguito è un esempio di base. Quali idee o migliori pratiche potrebbero essere utilizzate qui?Quanto è meglio mantenere un programma Go long running, in esecuzione?

Ho pensato di creare un canale e ritardare l'uscita di main ricevendo su detto canale, ma penso che potrebbe essere problematico se tutte le mie goroutine diventassero inattive ad un certo punto.

Nota a margine: nel mio server (non nell'esempio), il programma non è in realtà in esecuzione collegato a una shell, quindi non ha proprio senso interagire con la console. Per ora funziona, ma sto cercando il modo "corretto", assumendo che ce ne sia uno.

package main 

import (
    "fmt" 
    "time" 
) 

func main() { 
    go forever() 
    //Keep this goroutine from exiting 
    //so that the program doesn't end. 
    //This is the focus of my question. 
    fmt.Scanln() 
} 

func forever() { 
    for ; ; { 
    //An example goroutine that might run 
    //indefinitely. In actual implementation 
    //it might block on a chanel receive instead 
    //of time.Sleep for example. 
     fmt.Printf("%v+\n", time.Now()) 
     time.Sleep(time.Second) 
    } 
} 
+0

ho trovato 6 modi per farlo - http: // pliutau.it/different-ways-to-block-go-runtime-forever/ – pltvs

risposta

18

Il progetto corrente del runtime di Go presuppone che il programmatore sia responsabile di rilevare quando terminare una goroutine e quando terminare il programma. Il programmatore deve calcolare la condizione di terminazione per le goroutine e anche per l'intero programma. Un programma può essere terminato in modo normale chiamando lo os.Exit o ritornando dalla funzione main().

Creare un canale e ritardare l'uscita di main() ricevendo immediatamente su detto canale è un approccio valido per impedire main di uscire. Ma non risolve il problema di rilevare quando terminare il programma.

Se il numero di goroutines non può essere calcolato prima che la funzione main() entra nel ciclo di attesa-per-tutte-goroutines-to-terminare, è necessario essere l'invio di delta in modo che main funzione può tenere traccia di quante goroutines sono in volo:

// Receives the change in the number of goroutines 
var goroutineDelta = make(chan int) 

func main() { 
    go forever() 

    numGoroutines := 0 
    for diff := range goroutineDelta { 
     numGoroutines += diff 
     if numGoroutines == 0 { os.Exit(0) } 
    } 
} 

// Conceptual code 
func forever() { 
    for { 
     if needToCreateANewGoroutine { 
      // Make sure to do this before "go f()", not within f() 
      goroutineDelta <- +1 

      go f() 
     } 
    } 
} 

func f() { 
    // When the termination condition for this goroutine is detected, do: 
    goroutineDelta <- -1 
} 

Un approccio alternativo è sostituire il canale con sync.WaitGroup. Un inconveniente di questo approccio è che wg.Add(int) deve essere chiamato prima di chiamare wg.Wait(), quindi è necessario creare almeno 1 goroutine in main() mentre goroutines successive possono essere creati in qualsiasi parte del programma:

var wg sync.WaitGroup 

func main() { 
    // Create at least 1 goroutine 
    wg.Add(1) 
    go f() 

    go forever() 
    wg.Wait() 
} 

// Conceptual code 
func forever() { 
    for { 
     if needToCreateANewGoroutine { 
      wg.Add(1) 
      go f() 
     } 
    } 
} 

func f() { 
    // When the termination condition for this goroutine is detected, do: 
    wg.Done() 
} 
+0

Risposta completa e grande spiegazione. Probabilmente andrò con un canale di ricezione, dato che le mie routine di go sono sepolte in altri pacchetti che servono il servizio. Non voglio fare il conteggio delle goroutine ovunque. Il canale funzionerà bene se deciderò di aggiungere la logica di spegnimento al programma utilizzando segnali o qualche altro IPC ... Grazie! – Nate

1

Si potrebbe demonizzare il processo utilizzando Supervisor (http://supervisord.org/). La tua funzione per sempre sarebbe solo un processo che esegue e gestirà la parte principale della tua funzione. Dovresti usare l'interfaccia di controllo supervisore per avviare/spegnere/controllare il tuo processo.

+0

Un sistema interessante, ma sembra che sia un programma vero e proprio, non un codice Go che userei. Ho intenzione di modificare la mia domanda, poiché sto specificatamente cercando di capire le migliori pratiche per impedire che l'main esca. In questo momento sto usando fmt.Scanln(), voglio sapere se c'è un modo migliore ... Ma grazie per la segnalazione a Supervisor. Potrei vederci indagare ulteriormente. – Nate

36

Block per sempre . Ad esempio,

package main 

import (
    "fmt" 
    "time" 
) 

func main() { 
    go forever() 
    select {} // block forever 
} 

func forever() { 
    for { 
     fmt.Printf("%v+\n", time.Now()) 
     time.Sleep(time.Second) 
    } 
} 
+0

Semplice. Ottiene il lavoro fatto. Upvote. – Nate

+0

questo approccio sembra non funzionare più nell'ultimo go, genera l'errore 'goroutine 1 [seleziona (nessun caso)]: main.main()' –

+1

@dark_ruby: Funziona sull'ultima versione di Go: 'go versione devel + 13c44d2 Sab Jun 20 10:35:38 2015 +0000 linux/amd64'. Come possiamo riprodurre esattamente il tuo fallimento? Per essere precisi, ad esempio, "l'ultima novità" è troppo vaga. – peterSO

1

Ecco un semplice blocco per sempre utilizzando i canali

package main 

import (
    "fmt" 
    "time" 
) 

func main() { 
    done := make(chan bool) 
    go forever() 
    <-done // Block forever 
} 

func forever() { 
    for { 
     fmt.Printf("%v+\n", time.Now()) 
     time.Sleep(time.Second) 
    } 
} 
8

pacchetto di Go runtime ha una funzione chiamata runtime.Goexit che farà esattamente quello che vuoi.

Calling Goexit from the main goroutine terminates that goroutine without func main returning. Since func main has not returned, the program continues execution of other goroutines. If all other goroutines exit, the program crashes.

esempio nel playground

package main 

import (
    "fmt" 
    "runtime" 
    "time" 
) 

func main() { 
    go func() { 
     time.Sleep(time.Second) 
     fmt.Println("Go 1") 
    }() 
    go func() { 
     time.Sleep(time.Second * 2) 
     fmt.Println("Go 2") 
    }() 

    runtime.Goexit() 

    fmt.Println("Exit") 
} 
+0

Questo è molto bello. Ma, devo chiedermi, perché hanno scelto di far fallire il programma invece di farlo uscire con grazia. L'idea che avresti un altro goroutine gestisce l'arresto dell'applicazione e chiamerà exit quando riceve un segnale? Mi sto solo chiedendo come si possa usare correttamente questa funzione. – Nate

+1

@Nate è corretto. Per esempio controlla questo [codice] (https://play.golang.org/p/8N5Yr3ZNPT), vai routine 2 esce dal programma e il programma non si blocca. Se Goexit si arresta in modo anomalo, è perché tutte le altre routine Go sono state completate. Mi piace molto il modo in cui si blocca, fammi sapere che non sta facendo nulla, a differenza di dire "select {}" che bloccherebbe per sempre anche se non sta accadendo nulla. – jmaloney

+0

Ciò richiede più upvotes. –

Problemi correlati