programas - ¿Cuál es la mejor manera de mantener un programa Go de larga ejecución?
programas de mantenimiento (5)
Aquí hay un bloque simple para siempre usando 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)
}
}
Tengo un servidor de ejecución larga 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í?
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 pie: 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)
}
}
Bloquea para 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)
}
}
El diseño actual del tiempo de ejecución de Go asume que el programador es responsable de detectar cuándo terminar 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 a os.Exit
o regresando desde la función main()
.
Crear un canal y retrasar la salida de main()
recibiendo inmediatamente en dicho canal es un enfoque válido para evitar que main
cierre. Pero no resuelve el problema de detectar cuándo finalizar el programa.
Si el número de goroutines no se puede calcular antes de que la función main()
ingrese al bucle wait-for-all-goroutines-to-terminate, debe enviar deltas para que la función main
pueda 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 es reemplazar el canal con sync.WaitGroup
. Un inconveniente de este enfoque es que wg.Add(int)
necesita llamarse antes de llamar a wg.Wait()
, por lo que es necesario crear al menos 1 goroutine en main()
mientras que los 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()
}
El paquete de runtime de runtime de Go tiene una función llamada runtime.Goexit
que hará exactamente lo que usted desee.
Llamar a Goexit desde el goroutine principal termina ese goroutine sin func main return. Como func main no ha regresado, el programa continúa la ejecución de otros goroutines. Si todas las otras rutinas salen, el programa se bloquea.
Ejemplo en el 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")
}
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.