Sobes.tech
Middle

Jak poprawnie zakończyć wykonywanie wielu goroutine w Go?

sobes.tech AI

Odpowiedź od AI

Istnieje kilka powszechnych metod:

  • Użycie kontekstu (Context): Zalecane podejście do anulowania operacji w strukturze drzewa wywołań. Kontekst może przekazywać sygnał anulowania przez kanał.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d kończy się: %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Goroutine %d działa...\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() // Wysyłamy sygnał anulowania
    
        time.Sleep(1 * time.Second) // Czekamy na zakończenie goroutine
        fmt.Println("Główny program zakończony.")
    }
    
  • Użycie kanałów: Wysyłając sygnał (np. pustą strukturę struct{}) przez specjalny kanał, można poinformować goroutine o konieczności zakończenia.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(stopChan <-chan struct{}, id int) {
        for {
            select {
            case <-stopChan:
                fmt.Printf("Goroutine %d kończy się przez kanał\n", id)
                return
            default:
                fmt.Printf("Goroutine %d działa...\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) // Zamknięcie kanału sygnalizuje zakończenie
    
        time.Sleep(1 * time.Second) // Czekamy na zakończenie goroutine
        fmt.Println("Główny program zakończony.")
    }
    
  • Użycie sync.WaitGroup (do oczekiwania na zakończenie, nie do sygnalizacji): WaitGroup służy do oczekiwania na zakończenie grupy goroutine, ale sam nie zapewnia mechanizmu zatrzymania. Często jest używany w połączeniu z kontekstami lub kanałami.

    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
        defer wg.Done() // Zmniejsza licznik WaitGroup po zakończeniu goroutine
    
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d kończy się\n", id)
                return
            default:
                fmt.Printf("Goroutine %d działa...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        var wg sync.WaitGroup
    
        numWorkers := 2
        wg.Add(numWorkers) // Inicjalizacja WaitGroup
    
        for i := 1; i <= numWorkers; i++ {
            go worker(ctx, &wg, i)
        }
    
        time.Sleep(3 * time.Second)
        cancel() // Sygnał anulowania
    
        wg.Wait() // Czekanie na zakończenie wszystkich goroutine
        fmt.Println("Główny program zakończony.")
    }
    

Wybór metody zależy od konkretnego przypadku użycia i złożoności interakcji goroutine. Kontekst jest zazwyczaj preferowany do kaskadowych anulowań.