Wie man auf mehrere Goroutinen in Go wartet: 4 wesentliche Methoden
Takashi Yamamoto
Infrastructure Engineer · Leapcell

In Go muss die Haupt-Goroutine oft warten, bis andere Goroutinen ihre Aufgaben abgeschlossen haben, bevor sie die Ausführung fortsetzen oder das Programm beenden kann. Dies ist eine häufige Anforderung für die gleichzeitige Synchronisierung. Go bietet verschiedene Mechanismen, um dies zu erreichen, abhängig vom Szenario und den Anforderungen.
Methode 1: Verwendung von sync.WaitGroup
sync.WaitGroup
ist das am häufigsten verwendete Synchronisierungswerkzeug in Go, das entwickelt wurde, um auf den Abschluss einer Gruppe von Goroutinen zu warten. Es funktioniert über einen Zählermechanismus und eignet sich besonders, wenn die Haupt-Goroutine auf mehrere Sub-Goroutinen warten muss.
Beispielcode
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup // Starte 3 Goroutinen for i := 1; i <= 3; i++ { wg.Add(1) // Erhöhe den Zähler um 1 go func(id int) { defer wg.Done() // Verringere den Zähler um 1, wenn die Aufgabe abgeschlossen ist fmt.Printf("Goroutine %d läuft\n", id) }(i) } wg.Wait() // Die Haupt-Goroutine wartet, bis alle Goroutinen abgeschlossen sind fmt.Println("Alle Goroutinen abgeschlossen") }
Ausgabe (Reihenfolge kann variieren):
Goroutine 1 läuft Goroutine 2 läuft Goroutine 3 läuft Alle Goroutinen abgeschlossen
Wie es funktioniert:
wg.Add(n)
: Erhöht den Zähler, um die Anzahl der zu wartenden Goroutinen anzugeben.wg.Done()
: Wird von jeder Goroutine nach Abschluss aufgerufen und verringert den Zähler um 1.wg.Wait()
: Blockiert die Haupt-Goroutine, bis der Zähler Null erreicht.
Vorteile:
- Einfach und leicht zu bedienen, geeignet für eine feste Anzahl von Goroutinen.
- Keine zusätzlichen Kanäle erforderlich, geringer Performance-Overhead.
Methode 2: Verwendung von Channel
Durch das Senden von Signalen über Kanäle kann die Haupt-Goroutine warten, bis alle anderen Goroutinen Fertigstellungssignale gesendet haben. Diese Methode ist flexibler, aber normalerweise etwas komplexer als WaitGroup.
Beispielcode
package main import "fmt" func main() { fertig := make(chan struct{}) // Ein Signalkanal, um den Abschluss zu signalisieren numGoroutines := 3 for i := 1; i <= numGoroutines; i++ { go func(id int) { fmt.Printf("Goroutine %d läuft\n", id) fertig <- struct{}{} // Sende ein Signal, wenn die Aufgabe abgeschlossen ist }(i) } // Warte, bis alle Goroutinen abgeschlossen sind for i := 0; i < numGoroutines; i++ { <-fertig // Empfange Signale } fmt.Println("Alle Goroutinen abgeschlossen") }
Ausgabe (Reihenfolge kann variieren):
Goroutine 1 läuft Goroutine 2 läuft Goroutine 3 läuft Alle Goroutinen abgeschlossen
Wie es funktioniert:
- Jede Goroutine sendet nach Abschluss ein Signal an den
done
-Kanal. - Die Haupt-Goroutine bestätigt, dass alle Aufgaben erledigt sind, indem sie die angegebene Anzahl von Signalen empfängt.
Vorteile:
- Hohe Flexibilität, kann Daten (wie Aufgabenergebnisse) übertragen.
- Geeignet für eine dynamische Anzahl von Goroutinen.
Nachteile:
- Die Anzahl der Empfänge muss manuell verwaltet werden, was den Code etwas umständlich machen kann.
Methode 3: Kontrolle des Beendens mit Context
Die Verwendung von context.Context
ermöglicht es Ihnen, Goroutine-Beendigungen elegant zu steuern und die Haupt-Goroutine warten zu lassen, bis alle Aufgaben erledigt sind. Diese Methode ist besonders nützlich in Szenarien, die eine Abbruchfunktion oder Timeouts erfordern.
Beispielcode
package main import ( "context" "fmt" "sync" ) func main() { ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) go func(id int) { defer wg.Done() select { case <-ctx.Done(): fmt.Printf("Goroutine %d abgebrochen\n", id) return default: fmt.Printf("Goroutine %d läuft\n", id) } }(i) } // Simuliere den Abschluss der Aufgabe cancel() // Sende ein Abbruchsignal wg.Wait() // Warte, bis alle Goroutinen beendet sind fmt.Println("Alle Goroutinen abgeschlossen") }
Ausgabe (kann je nachdem, wann der Abbruch auftritt, variieren):
Goroutine 1 läuft Goroutine 2 läuft Goroutine 3 läuft Alle Goroutinen abgeschlossen
Wie es funktioniert:
- Der Kontext wird verwendet, um Goroutinen zu benachrichtigen, dass sie beendet werden sollen.
- WaitGroup stellt sicher, dass die Haupt-Goroutine wartet, bis alle Goroutinen abgeschlossen sind.
Vorteile:
- Unterstützt Abbruch und Timeout, geeignet für komplexe, gleichzeitige Szenarien.
Nachteile:
- Etwas komplexerer Code.
Methode 4: Verwendung von errgroup (Empfohlen)
golang.org/x/sync/errgroup
ist ein erweitertes Tool, das die Wartefunktionalität von WaitGroup mit der Fehlerbehandlung kombiniert, wodurch es sich besonders gut eignet, um auf eine Gruppe von Aufgaben zu warten und Fehler zu behandeln.
Beispielcode
package main import ( "fmt" "golang.org/x/sync/errgroup" ) func main() { var g errgroup.Group for i := 1; i <= 3; i++ { ID := i G.Go(func() error { fmt.Printf("Goroutine %d läuft\n", id) return nil // Kein Fehler }) } if err := g.Wait(); err != nil { fmt.Println("Fehler:", err) } else { fmt.Println("Alle Goroutinen abgeschlossen") } }
Ausgabe:
Goroutine 1 läuft Goroutine 2 läuft Goroutine 3 läuft Alle Goroutinen abgeschlossen
Wie es funktioniert:
g.Go()
startet eine Goroutine und fügt sie der Gruppe hinzu.g.Wait()
wartet, bis alle Goroutinen abgeschlossen sind, und gibt den ersten Fehler ungleich Null zurück (falls vorhanden).
Vorteile:
- Einfach und elegant, unterstützt die Fehlerweitergabe.
- Integrierte Kontextunterstützung (kann
errgroup.WithContext
verwenden).
Installation:
- Erfordert
go get golang.org/x/sync/errgroup
.
Welche Methode soll ich wählen?
sync.WaitGroup
- Anwendbare Szenarien: Einfache Aufgaben mit einer festen Anzahl.
- Vorteile: Einfach und effizient.
- Nachteile: Unterstützt keine Fehlerbehandlung oder Abbruchfunktion.
Channel
- Anwendbare Szenarien: Dynamische Aufgaben oder wenn Ergebnisse übergeben werden müssen.
- Vorteile: Sehr flexibel.
- Nachteile: Die manuelle Verwaltung ist komplexer.
context
- Anwendbare Szenarien: Komplexe Situationen, in denen ein Abbruch oder Timeout erforderlich ist.
- Vorteile: Unterstützt Abbruch und Timeout.
- Nachteile: Der Code ist etwas komplexer.
errgroup
- Anwendbare Szenarien: Moderne Anwendungen, die Fehlerbehandlung und Warten erfordern.
- Vorteile: Elegant und leistungsstark.
- Nachteile: Erfordert eine zusätzliche Abhängigkeit.
Andere: Warum schläft die Haupt-Goroutine nicht einfach?
time.Sleep
führt nur eine feste Verzögerung ein und kann nicht genau warten, bis Aufgaben abgeschlossen sind. Dies kann dazu führen, dass das Programm vorzeitig beendet wird oder zu unnötigem Warten führt. Synchronisierungswerkzeuge sind zuverlässiger.
Zusammenfassung
Die am häufigsten verwendete Methode für die Haupt-Goroutine, um auf andere Goroutinen zu warten, ist sync.WaitGroup
, die einfach und effizient ist. Wenn Sie Fehlerbehandlungs- oder Abbruchfunktionen benötigen, wird errgroup
oder eine Kombination mit context
empfohlen. Wählen Sie das geeignete Werkzeug entsprechend Ihren spezifischen Anforderungen, um eine klare Programmlogik zu gewährleisten und Ressourcenlecks zu vermeiden.
Wir sind Leapcell, Ihre erste Wahl für das Hosten von Go-Projekten.
Leapcell ist die Serverless-Plattform der nächsten Generation für Webhosting, asynchrone Aufgaben und Redis:
Multi-Language Support
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Stellen Sie unbegrenzt Projekte kostenlos bereit
- zahlen Sie nur für die Nutzung – keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 US-Dollar unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für eine mühelose Einrichtung.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für verwertbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Kein Betriebsaufwand – konzentrieren Sie sich einfach auf den Aufbau.
Erfahren Sie mehr in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ