Sobes.tech
Middle

Come terminare correttamente l'esecuzione di più goroutine in Go?

sobes.tech AI

Risposta dell'AI

Esistono diversi metodi comuni:

  • Utilizzo del contesto (Context): Approccio raccomandato per annullare le operazioni in una struttura ad albero di chiamate. Il contesto può trasmettere un segnale di annullamento tramite un canale.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d si termina: %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Goroutine %d in esecuzione...\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() // Invia il segnale di annullamento
    
        time.Sleep(1 * time.Second) // Attende che le goroutine terminino
        fmt.Println("Programma principale terminato.")
    }
    
  • Utilizzo dei canali: Inviando un segnale (ad esempio, una struttura vuota struct{}) tramite un canale speciale, si può informare la goroutine che deve terminare.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(stopChan <-chan struct{}, id int) {
        for {
            select {
            case <-stopChan:
                fmt.Printf("Goroutine %d si termina tramite canale\n", id)
                return
            default:
                fmt.Printf("Goroutine %d in esecuzione...\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 chiusura del canale indica che deve terminare
    
        time.Sleep(1 * time.Second) // Attende che le goroutine terminino
        fmt.Println("Programma principale terminato.")
    }
    
  • Utilizzo di sync.WaitGroup (per aspettare la fine, non per segnalare): WaitGroup viene usato per aspettare che un gruppo di goroutine finisca, ma non fornisce un meccanismo di arresto. Viene spesso combinato con contesti o canali.

    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
        defer wg.Done() // Diminuisce il contatore del WaitGroup al termine
    
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d si termina\n", id)
                return
            default:
                fmt.Printf("Goroutine %d in esecuzione...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        var wg sync.WaitGroup
    
        numWorkers := 2
        wg.Add(numWorkers) // Inizializza il WaitGroup
    
        for i := 1; i <= numWorkers; i++ {
            go worker(ctx, &wg, i)
        }
    
        time.Sleep(3 * time.Second)
        cancel() // Segnale di annullamento
    
        wg.Wait() // Attende che tutte le goroutine finiscano
        fmt.Println("Programma principale terminato.")
    }
    

La scelta del metodo dipende dal caso d'uso specifico e dalla complessità delle interazioni tra goroutine. In generale, il contesto è preferito per cancellazioni a cascata.