sync/errgroup pour Go : guide complet avec exemples
1. Qu'est-ce que c'est et pourquoi l'utiliser
La bibliothèque sync/errgroup fait partie de l'extension officielle golang.org/x/sync. Elle offre un moyen puissant et élégant de gérer des goroutines concurrentes avec une gestion d'erreurs intégrée. Dans la bibliothèque standard de Go, le package sync propose WaitGroup, mais il ne permet pas de collecter les erreurs retournées par les goroutines. errgroup résout ce problème : vous lancez plusieurs goroutines, et si l'une d'elles retourne une erreur, le groupe le signale immédiatement (ou à la fin de toutes), annule le contexte et permet aux autres goroutines de se terminer proprement.
Pourquoi est-ce utile ? Dans les applications réelles, on doit souvent exécuter plusieurs tâches indépendantes en parallèle : télécharger des fichiers, traiter des données, effectuer des appels API. Si une tâche échoue, il est inutile de continuer les autres : il faut s'arrêter rapidement et retourner l'erreur à l'utilisateur. errgroup le fait automatiquement : vous passez un contexte, et dès la première erreur, le contexte est annulé, permettant à toutes les goroutines qui l'écoutent de se terminer prématurément.
C'est particulièrement utile dans les microservices, les serveurs web et les utilitaires en ligne de commande, où la réactivité aux erreurs est cruciale. Contrairement à une gestion manuelle avec des canaux et select, errgroup fournit une API propre et déclarative, difficile à utiliser incorrectement.
2. Installation
sync/errgroup ne fait pas partie de la bibliothèque standard de Go, mais est un sous-module de golang.org/x/sync. L'installation se fait en une seule commande :
go get golang.org/x/sync/errgroup
Ensuite, vous pouvez importer le package dans votre code :
import "golang.org/x/sync/errgroup"
Assurez-vous d'avoir Go version 1.13 ou supérieure (1.18+ recommandé pour la prise en charge des generics, bien que errgroup ne les utilise pas). Si vous utilisez des modules (go.mod), la commande go get mettra automatiquement à jour le fichier go.sum.
3. Démarrage rapide — exemple minimal fonctionnel
Voici un exemple simple qui lance trois goroutines, chacune retournant une erreur si le nombre passé est négatif. Si au moins une goroutine retourne une erreur, errgroup.Wait() la retournera.
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 // capture de variable
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
if n < 0 {
return fmt.Errorf("nombre négatif : %d", n)
}
fmt.Printf("Traité %d\n", n)
return nil
})
}
if err := g.Wait(); err != nil {
fmt.Printf("Erreur : %v\n", err)
} else {
fmt.Println("Toutes les tâches terminées avec succès")
}
}
Sortie : Traiter 1, Traiter 3, Erreur : nombre négatif : -2. Notez que la goroutine avec le nombre 3 aurait pu ne pas s'exécuter si le contexte avait été annulé avant son lancement — cela illustre le mécanisme d'arrêt précoce.
4. Méthodes et fonctions principales
Voici tous les éléments clés de l'API du package errgroup. Chaque méthode est accompagnée de sa signature, d'une description et d'un exemple fonctionnel.
4.1. WithContext
Signature : func WithContext(ctx context.Context) (*Group, context.Context)
Description : Crée un nouveau groupe errgroup et un contexte dérivé. Le contexte retourné est automatiquement annulé dès que l'une des goroutines du groupe retourne une erreur. C'est la fonction d'entrée principale pour utiliser errgroup.
Exemple :
g, ctx := errgroup.WithContext(context.Background())
// ctx sera annulé si une goroutine de g retourne une erreur
4.2. Go
Signature : func (g *Group) Go(f func() error)
Description : Lance une goroutine qui exécute la fonction f. Si f retourne une erreur non nulle, cette erreur est enregistrée et le contexte du groupe est annulé. La méthode retourne immédiatement ; elle ne bloque pas.
Exemple :
g.Go(func() error {
// effectuer une tâche
if err := doSomething(); err != nil {
return err // cela annulera le contexte
}
return nil
})
4.3. Wait
Signature : func (g *Group) Wait() error
Description : Bloque jusqu'à ce que toutes les goroutines lancées via Go soient terminées. Retourne la première erreur non nulle rencontrée, ou nil si toutes les goroutines ont réussi. C'est la méthode de synchronisation finale.
Exemple :
if err := g.Wait(); err != nil {
log.Fatalf("Une tâche a échoué : %v", err)
}
log.Println("Toutes les tâches ont réussi")
4.4. SetLimit (Go 1.19+)
Signature : func (g *Group) SetLimit(n int)
Description : Limite le nombre de goroutines actives simultanément à n. Si n est inférieur ou égal à 0, la limite est supprimée (goroutines illimitées). Utile pour contrôler l'utilisation des ressources (par exemple, limiter les connexions réseau simultanées).
Exemple :
g := new(errgroup.Group)
g.SetLimit(5) // maximum 5 goroutines simultanées
for _, task := range tasks {
task := task
g.Go(func() error {
return processTask(task)
})
}
if err := g.Wait(); err != nil {
// gérer l'erreur
}
4.5. TryGo (Go 1.19+)
Signature : func (g *Group) TryGo(f func() error) bool
Description : Tente de lancer une goroutine. Retourne true si la goroutine a été lancée avec succès, false si la limite de goroutines simultanées est atteinte. Contrairement à Go, elle ne bloque pas si la limite est atteinte.
Exemple :
if !g.TryGo(func() error {
return doWork()
}) {
fmt.Println("Limite de goroutines atteinte, tâche mise en attente")
}