Sobes.tech
Middle

Comment terminer correctement l'exécution de plusieurs goroutines en Go?

sobes.tech IA

Réponse de l'IA

Il existe plusieurs méthodes courantes :

  • Utilisation du contexte (Context) : Approche recommandée pour annuler des opérations dans une structure arborescente d'appels. Le contexte peut transmettre un signal d'annulation via un canal.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d se termine : %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Goroutine %d en cours d'exécution...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
    
        go worker(ctx, 1)
        go worker(ctx, 2)
    
        time.Sleep(3 * time.Second)
        cancel() // Envoi du signal d'annulation
    
        time.Sleep(1 * time.Second) // Attente de la terminaison des goroutines
        fmt.Println("Programme principal terminé.")
    }
    
  • Utilisation de canaux : En envoyant un signal (par exemple, une structure vide struct{}) via un canal spécial, on peut informer la goroutine qu'elle doit se terminer.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(stopChan <-chan struct{}, id int) {
        for {
            select {
            case <-stopChan:
                fmt.Printf("Goroutine %d se termine via canal\n", id)
                return
            default:
                fmt.Printf("Goroutine %d en cours d'exécution...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        stopChan := make(chan struct{})
    
        go worker(stopChan, 1)
        go worker(stopChan, 2)
    
        time.Sleep(3 * time.Second)
        close(stopChan) // La fermeture du canal indique la fin
    
        time.Sleep(1 * time.Second) // Attente de la terminaison des goroutines
        fmt.Println("Programme principal terminé.")
    }
    
  • Utilisation de sync.WaitGroup (pour attendre la fin, pas pour signaler) : WaitGroup est utilisé pour attendre la fin d'un groupe de goroutines, mais ne fournit pas de mécanisme d'arrêt en soi. Il est souvent combiné avec un contexte ou des canaux.

    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
        defer wg.Done() // Décrémente le compteur du WaitGroup à la fin
    
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d se termine\n", id)
                return
            default:
                fmt.Printf("Goroutine %d en cours d'exécution...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        var wg sync.WaitGroup
    
        numWorkers := 2
        wg.Add(numWorkers) // Initialisation du WaitGroup
    
        for i := 1; i <= numWorkers; i++ {
            go worker(ctx, &wg, i)
        }
    
        time.Sleep(3 * time.Second)
        cancel() // Signal d'annulation
    
        wg.Wait() // Attente de la fin de toutes les goroutines
        fmt.Println("Programme principal terminé.")
    }
    

Le choix de la méthode dépend du cas d'utilisation spécifique et de la complexité des interactions entre goroutines. Le contexte est généralement préféré pour les annulations en cascade.