2012-03-03 13 views
29

Tengo un servidor de larga ejecución escrito en Go. Se apaga varios gorutines donde se ejecuta la lógica del programa. Después de eso principal no hace nada útil. Una vez que las salidas principales, el programa se cerrará. El método que estoy usando ahora para mantener el programa en ejecución es solo una simple llamada a fmt.Scanln(). Me gustaría saber cómo otros evitan que salga principal. A continuación se muestra un ejemplo básico. ¿Qué ideas o mejores prácticas podrían usarse aquí?¿Cuál es la mejor forma de mantener un programa Go de larga ejecución?

Consideré crear un canal y retrasar la salida de main al recibir en dicho canal, pero creo que podría ser problemático si todos mis goroutines se vuelven inactivos en algún momento.

Nota al margen: en mi servidor (no en el ejemplo), el programa no se está conectando a un shell, por lo que no tiene sentido interactuar con la consola de todos modos. Por ahora funciona, pero estoy buscando la forma "correcta", suponiendo que haya una.

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

me encontró 6 maneras de hacerlo - http: // pliutau.com/different-ways-to-block-go-runtime-forever/ – pltvs

Respuesta

18

El diseño actual del tiempo de ejecución de Go asume que el programador es responsable de detectar cuándo finalizar una rutina y cuándo finalizar el programa. El programador necesita calcular la condición de terminación para los goroutines y también para todo el programa. Un programa puede finalizar de manera normal llamando al os.Exit o volviendo desde la función main().

Crear un canal y retrasar la salida de main() recibiendo inmediatamente en dicho canal es un método válido para evitar que main salga. Pero no resuelve el problema de detectar cuándo finalizar el programa.

Si el número de goroutines no puede ser calculado antes de la función main() entra en el bucle de espera-para-todo-goroutines a terminar, tiene que ser el envío de los deltas de manera que main función se puede realizar un seguimiento de cuántos goroutines están en vuelo:

// 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 enfoque alternativo consiste en reemplazar el canal con sync.WaitGroup. Un inconveniente de este enfoque es que wg.Add(int) tiene que ser llamado antes de llamar wg.Wait(), por lo que es necesario crear al menos 1 goroutine en main() mientras goroutines posteriores se pueden crear en cualquier parte del programa:

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

Respuesta completa y gran explicación. Es probable que vaya con un canal de recepción, ya que mis rutinas de ir están enterradas en otros paquetes que sirven. No quiero hacer goroutine contando en todas partes. El canal funcionará bien si decido agregar lógica de apagado al programa usando señales o algún otro IPC ... ¡Gracias! – Nate

1

Usted puede demonizar el proceso utilizando Supervisor (http://supervisord.org/). Su función siempre sería un proceso que se ejecuta, y manejaría la parte de la función principal. Utilizaría la interfaz de control supervisor para iniciar/apagar/verificar su proceso.

+0

Un sistema interesante, pero parece que es un programa real, no el código Go que usaría. Voy a editar mi pregunta, ya que estoy tratando específicamente de descubrir las mejores prácticas para evitar que main se cierre. En este momento, estoy usando fmt.Scanln(), quiero saber si hay una mejor manera ... Pero gracias por el consejo al Supervisor. Podría vernos investigando más. – Nate

36

Bloquear siempre . Por ejemplo,

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

Simple. Obtiene el trabajo hecho. Upvote. – Nate

+0

este enfoque ya no parece funcionar en la última marcha, arroja el siguiente error 'goroutine 1 [select (no cases)]: main.main()' –

+1

@dark_ruby: Funciona en la última versión de Go: 'go versión devel + 13c44d2 sáb 20 jun 10:35:38 2015 +0000 linux/amd64'. ¿Cómo podemos reproducir tu falla exactamente? Sea muy preciso, por ejemplo, "último intento" es demasiado vago. – peterSO

1

Aquí es un simple bloque para siempre el uso de canales

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

paquete de Go runtime tiene una función llamada runtime.Goexit que va a hacer exactamente lo que quiere.

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.

en el Ejemplo 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

Eso es genial. Pero me pregunto por qué eligieron hacer que el programa fallara en lugar de tener que salir con gracia. ¿Es la idea de que tenga otro administrador de rutina que maneje el cierre de la aplicación y que llame a la salida cuando reciba una señal? Me pregunto cómo se usaría correctamente esta función. – Nate

+1

@Nate que es correcto. Por ejemplo, consulte este [código] (https://play.golang.org/p/8N5Yr3ZNPT), la rutina Ir 2 sale del programa y el programa no falla. Si Goexit bloquea su programa, se debe a que todas las demás rutinas de Go se han completado. Me gusta bastante cómo se bloquea, háganos saber que no está haciendo nada, a diferencia de 'select {}' que bloquearía para siempre aunque no ocurra nada. – jmaloney

+0

Esto necesita más votos positivos. –

Cuestiones relacionadas