J'ai un serveur de longue durée écrit en Go. Main déclenche plusieurs goroutines où la logique du programme s'exécute. Après cela, le principal ne fait rien d'utile. Une fois les sorties principales terminées, le programme se ferme. La méthode que j'utilise en ce moment pour maintenir le programme en marche n'est qu'un simple appel à fmt.Scanln (). J'aimerais savoir comment les autres empêchent de sortir. Voici un exemple de base. Quelles idées ou meilleures pratiques pourraient être utilisées ici?
J'ai envisagé de créer un canal et de retarder la sortie du principal en recevant sur ledit canal, mais je pense que cela pourrait être problématique si tous mes goroutines deviennent inactifs à un moment donné.
Note latérale: Sur mon serveur (pas dans l'exemple), le programme ne fonctionne pas réellement connecté à un Shell, il n'a donc pas vraiment de sens d'interagir avec la console de toute façon. Pour l'instant cela fonctionne, mais je cherche la "bonne" façon, en supposant qu'il y en ait une.
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)
}
}
La conception actuelle de l'exécution de Go suppose que le programmeur est responsable de détecter quand mettre fin à un goroutine et quand mettre fin au programme. Le programmeur doit calculer la condition de terminaison pour les goroutines et également pour l'ensemble du programme. Un programme peut être terminé de manière normale en appelant os.Exit
ou en revenant de la fonction main()
.
Créer un canal et retarder la sortie de main()
en recevant immédiatement sur ledit canal est une approche valide pour empêcher main
de sortir. Mais cela ne résout pas le problème de détecter quand mettre fin au programme.
Si le nombre de goroutines ne peut pas être calculé avant que la fonction main()
entre dans la boucle wait-for-all-goroutines-to-terminate, vous devez envoyer des deltas pour que la fonction main
puisse conserver suivre le nombre de goroutines en vol:
// 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
}
Une approche alternative consiste à remplacer le canal par sync.WaitGroup
. Un inconvénient de cette approche est que wg.Add(int)
doit être appelé avant d'appeler wg.Wait()
, il est donc nécessaire de créer au moins 1 goroutine dans main()
tandis que les goroutines suivants peuvent être créé dans n'importe quelle partie du programme:
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()
}
Bloquer pour toujours. Par exemple,
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)
}
}
Le package runtime de Go a une fonction appelée runtime.Goexit
qui fera exactement ce que vous voulez.
L'appel de Goexit à partir du goroutine principal met fin à ce goroutine sans retour de la fonction principale. Puisque func main n'est pas revenu, le programme continue l'exécution d'autres goroutines. Si tous les autres goroutines sortent, le programme plante.
Exemple dans le aire de jeux
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")
}
Voici un bloc simple pour toujours en utilisant des canaux
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)
}
}
Vous pouvez démonifier le processus en utilisant Supervisor ( http://supervisord.org/ ). Votre fonction ne serait pour toujours qu'un processus qu'elle exécute, et elle gérerait la partie de votre fonction principale. Vous utiliseriez l'interface de contrôle du superviseur pour démarrer/arrêter/vérifier votre processus.