Sobes.tech
Middle

Wie beendet man die Ausführung mehrerer Goroutines in Go richtig?

sobes.tech KI

Antwort von AI

Es gibt mehrere gängige Methoden:

  • Verwendung des Kontexts (Context): Empfohlener Ansatz zum Abbrechen von Operationen in einer Baumstruktur von Aufrufen. Der Kontext kann ein Abbruchsignal über einen Kanal übertragen.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func worker(ctx context.Context, id int) {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d wird beendet: %v\n", id, ctx.Err())
                return
            default:
                fmt.Printf("Goroutine %d arbeitet...\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() // Senden des Abbruchs-Signals
    
        time.Sleep(1 * time.Second) // Warten, bis die Goroutines beendet sind
        fmt.Println("Hauptprogramm beendet.")
    }
    
  • Verwendung von Kanälen: Durch das Senden eines Signals (z.B. eine leere Struktur struct{}) über einen speziellen Kanal kann eine Goroutine über die Beendigung informiert werden.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func worker(stopChan <-chan struct{}, id int) {
        for {
            select {
            case <-stopChan:
                fmt.Printf("Goroutine %d beendet über Kanal\n", id)
                return
            default:
                fmt.Printf("Goroutine %d arbeitet...\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) // Schließen des Kanals signalisiert das Ende
    
        time.Sleep(1 * time.Second) // Warten, bis die Goroutines beendet sind
        fmt.Println("Hauptprogramm beendet.")
    }
    
  • Verwendung von sync.WaitGroup (zum Warten auf Beendigung, nicht zum Signalisieren): WaitGroup wird verwendet, um auf das Ende einer Gruppe von Goroutines zu warten, bietet aber keinen Mechanismus zum Anhalten. Es wird oft in Kombination mit Kontexten oder Kanälen verwendet.

    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
        defer wg.Done() // Verringert den Zähler des WaitGroup bei Beendigung der Goroutine
    
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("Goroutine %d wird beendet\n", id)
                return
            default:
                fmt.Printf("Goroutine %d arbeitet...\n", id)
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        var wg sync.WaitGroup
    
        numWorkers := 2
        wg.Add(numWorkers) // Initialisierung des WaitGroup
    
        for i := 1; i <= numWorkers; i++ {
            go worker(ctx, &wg, i)
        }
    
        time.Sleep(3 * time.Second)
        cancel() // Abbruchsignal
    
        wg.Wait() // Warten auf alle Goroutines
        fmt.Println("Hauptprogramm beendet.")
    }
    

Die Wahl der Methode hängt vom spezifischen Anwendungsfall und der Komplexität der Goroutine-Interaktionen ab. Der Kontext wird im Allgemeinen für Kaskadencancellationen bevorzugt.