sync/errgroup für Go: Vollständige Anleitung mit Beispielen
1. Was ist sync/errgroup und warum wird es benötigt?
Die Bibliothek sync/errgroup ist Teil der offiziellen Erweiterung golang.org/x/sync und bietet eine leistungsstarke und elegante Möglichkeit, parallele Goroutinen mit Fehlerbehandlung zu steuern. Die Go-Standardbibliothek enthält das Paket sync mit WaitGroup, das jedoch keine Fehler von Goroutinen sammeln kann. errgroup löst dieses Problem: Sie starten mehrere Goroutinen, und wenn mindestens eine einen Fehler zurückgibt, signalisiert die Gruppe dies sofort (oder nach Abschluss aller), bricht den Kontext ab und ermöglicht den anderen Goroutinen, sich ordnungsgemäß zu beenden.
Wozu braucht man das? In realen Anwendungen müssen oft mehrere unabhängige Aufgaben parallel ausgeführt werden: Dateien herunterladen, Daten verarbeiten, API-Anfragen stellen. Wenn eine Aufgabe mit einem Fehler fehlschlägt, macht es keinen Sinn, die anderen fortzusetzen – man muss schnell anhalten und den Fehler an den Benutzer zurückgeben. errgroup erledigt dies automatisch: Sie übergeben einen Kontext, und beim ersten Fehler wird der Kontext abgebrochen, sodass alle Goroutinen, die auf diesen Kontext hören, vorzeitig beendet werden können.
Dies ist besonders nützlich in Microservices, Webservern und CLI-Tools, bei denen eine schnelle Reaktion auf Fehler wichtig ist. Im Gegensatz zur manuellen Steuerung mit Channels und select bietet errgroup eine saubere, deklarative API, die kaum falsch verwendet werden kann.
2. Installation
sync/errgroup ist nicht Teil der Go-Standardbibliothek, sondern gehört zum Untermodul golang.org/x/sync. Die Installation erfolgt mit einem einzigen Befehl:
go get golang.org/x/sync/errgroup
Danach können Sie das Paket in Ihren Code importieren:
import "golang.org/x/sync/errgroup"
Stellen Sie sicher, dass Sie Go Version 1.13 oder höher installiert haben (empfohlen wird 1.18+ für die Unterstützung von Generics, auch wenn errgroup diese nicht verwendet). Wenn Sie Module (go.mod) verwenden, aktualisiert der Befehl go get automatisch die Datei go.sum.
3. Schnellstart – Minimales funktionsfähiges Beispiel
Nachfolgend finden Sie ein einfaches Beispiel, das drei Goroutinen startet, die jeweils einen Fehler zurückgeben, wenn die übergebene Zahl negativ ist. Wenn mindestens eine Goroutine einen Fehler zurückgibt, gibt errgroup.Wait() diesen zurück.
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
g, ctx := errgroup.WithContext(context.Background())
numbers := []int{1, -2, 3}
for _, n := range numbers {
n := n // Variable erfassen
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
if n < 0 {
return fmt.Errorf("negative Zahl: %d", n)
}
fmt.Printf("Verarbeitet %d\n", n)
return nil
})
}
if err := g.Wait(); err != nil {
fmt.Printf("Fehler: %v\n", err)
} else {
fmt.Println("Alle Aufgaben erfolgreich abgeschlossen")
}
}
Ausgabe: Verarbeitet 1, Verarbeitet 3, Fehler: negative Zahl: -2. Beachten Sie, dass die Goroutine mit der Zahl 3 möglicherweise nicht ausgeführt wurde, wenn der Kontext vor ihrem Start abgebrochen wurde – dies demonstriert den Mechanismus des vorzeitigen Stoppens.
4. Wichtige Methoden/Funktionen
Nachfolgend sind alle wichtigen API-Elemente des Pakets errgroup aufgeführt. Jede Methode wird mit Signatur, Beschreibung und einem funktionsfähigen Beispiel vorgestellt.
4.1. WithContext
Signatur: func WithContext(ctx context.Context) (*Group, context.Context)
Beschreibung: Erstellt eine neue errgroup und einen abgeleiteten Kontext. Der zurückgegebene Kontext wird automatisch abgebrochen, wenn die erste Goroutine einen Fehler zurückgibt. Dies ist die gebräuchlichste Methode zum Erstellen einer Gruppe.
Beispiel:
g, ctx := errgroup.WithContext(context.Background())
g.Go(func() error {
// Ihre Aufgabe hier
return nil
})
4.2. Go
Signatur: func (g *Group) Go(f func() error)
Beschreibung: Startet eine neue Goroutine, die die übergebene Funktion ausführt. Wenn die Funktion einen Fehler zurückgibt, wird dieser gespeichert und der Kontext (falls vorhanden) abgebrochen. Die Methode ist blockierungsfrei – sie kehrt sofort zurück.
Beispiel:
g.Go(func() error {
resp, err := http.Get("https://example.com")
if err != nil {
return err
}
defer resp.Body.Close()
// Verarbeitung...
return nil
})
4.3. Wait
Signatur: func (g *Group) Wait() error
Beschreibung: Blockiert, bis alle von Go gestarteten Goroutinen beendet sind. Gibt den ersten aufgetretenen Fehler zurück (oder nil, wenn alle erfolgreich waren). Diese Methode sollte typischerweise einmal am Ende aufgerufen werden.
Beispiel:
if err := g.Wait(); err != nil {
log.Fatalf("Fehler in einer Goroutine: %v", err)
}
fmt.Println("Alle Aufgaben erfolgreich abgeschlossen")
4.4. SetLimit (Go 1.19+)
Signatur: func (g *Group) SetLimit(n int)
Beschreibung: Begrenzt die Anzahl der gleichzeitig aktiven Goroutinen auf n. Wenn n kleiner oder gleich 0 ist, ist die Anzahl der Goroutinen unbegrenzt. Dies ist nützlich, um Ressourcen zu schonen, z. B. bei vielen API-Anfragen.
Beispiel:
g := new(errgroup.Group)
g.SetLimit(5) // Maximal 5 gleichzeitige Goroutinen
for i := 0; i < 20; i++ {
i := i
g.Go(func() error {
fmt.Printf("Starte Aufgabe %d\n", i)
time.Sleep(time.Second)
return nil
})
}
g.Wait()
4.5. TryGo (Go 1.19+)
Signatur: func (g *Group) TryGo(f func() error) bool
Beschreibung: Versucht, eine neue Goroutine zu starten, ohne zu blockieren. Gibt true zurück, wenn die Goroutine gestartet wurde, und false, wenn das Limit erreicht ist. Nützlich für nicht-blockierende Workflows.
Beispiel:
if !g.TryGo(func() error {
// Aufgabe ausführen
return nil
}) {
fmt.Println("Limit erreicht, Aufgabe wird nicht ausgeführt")
}
Auch in der Bibliothek
Generierung von PDF-Berichten in Swift für iOS und macOS mit PDFKit und Core Graphics
PDFKit für JavaScript (Node.js): Komplettguide mit Beispielen
Arbeiten mit CSV in Java: Bibliotheken OpenCSV und Apache Commons CSV
Multer für JavaScript (Node.js): Komplettguide mit Beispielen