¿Cuál es la mejor manera de mantener un programa Go de larga duración, funcionando?


Tengo un servidor de larga duración escrito en Go. Main dispara varios goroutines donde se ejecuta la lógica del programa. Después de esto principal no hace nada útil. Una vez que main salga, el programa se cerrará. El método que estoy usando en este momento para mantener el programa en funcionamiento es solo una simple llamada a fmt.Scanln (). Me gustaría saber cómo otros evitan que Main salga. A continuación se muestra un ejemplo básico. ¿Qué ideas o mejores prácticas podrían utilizarse aquí?

Consideré crear un canal y retrasar la salida de principal por 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á ejecutando realmente conectado a un shell, por lo que realmente no tiene sentido interactuar con la consola de todos modos. Por ahora funciona, pero estoy buscando la manera "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)
    }
}
Author: Nate, 2012-03-03

5 answers

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

Crear un canal y retrasar la salida de main() al recibir inmediatamente en dicho canal es válido enfoque de prevenir main de salir. Pero no resuelve el problema de detectar cuándo terminar el programa.

Si el número de goroutinas no se puede calcular antes de que la función main() entre en el bucle wait-for-all-goroutines-to-terminate, debe enviar deltas para que la función main pueda realizar un seguimiento de cuántas goroutinas 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 ser llamado antes de llamar a wg.Wait(), por lo que es necesario crear al menos 1 goroutine en main() mientras que las 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()
}
 20
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-03 10:01:24

Bloquear 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)
    }
}
 45
Author: peterSO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-03 09:48:20

El paquete Go's runtime tiene una función llamada runtime.Goexit eso hará exactamente lo que quieres.

Llamando a Goexit desde la goroutine principal termina esa goroutine sin el regreso de func main. Desde func main no ha regresado, el programa continúa la ejecución de otros goroutines. Si todas las demás goroutines salen, el programa se bloquea.

Ejemplo en el patio de recreo

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")
}
 13
Author: jmaloney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-15 17:59:31

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

 1
Author: inlinestyle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-03 05:54:47

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)
    }
}
 1
Author: Baba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-02 13:09:37