web-dev-qa-db-fra.com

Comment puis-je conserver un programme Go de longue durée en cours d'exécution?

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)
    }
}
44
Nate

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()
}
28
user811773

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)
    }
}
58
peterSO

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")
}
18
jmaloney

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)
    }
}
3
Baba

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.

0
inlinestyle