sync/errgroup en Go: guía completa con ejemplos prácticos
1. ¿Qué es y para qué sirve?
La librería sync/errgroup es parte de la extensión oficial golang.org/x/sync y proporciona una forma potente y elegante de gestionar gorutinas concurrentes con manejo de errores. En la biblioteca estándar de Go existe el paquete sync con WaitGroup, pero este no puede recolectar errores de las gorutinas. errgroup resuelve este problema: ejecutas varias gorutinas y, si al menos una devuelve un error, el grupo lo notifica de inmediato (o al finalizar todas), cancelando el contexto y permitiendo que las demás gorutinas finalicen correctamente.
¿Para qué sirve? En aplicaciones reales, a menudo es necesario ejecutar varias tareas independientes en paralelo: descargar archivos, procesar datos, hacer solicitudes a APIs. Si una tarea falla, no tiene sentido continuar con las demás; es mejor detenerse rápidamente y devolver el error al usuario. errgroup hace esto automáticamente: pasas un contexto y, ante el primer error, el contexto se cancela y todas las gorutinas que escuchan ese contexto pueden finalizar antes de tiempo.
Esto es especialmente útil en microservicios, servidores web y utilidades CLI, donde la velocidad de reacción ante errores es crucial. A diferencia de la gestión manual con canales y select, errgroup ofrece una API limpia y declarativa que es difícil de usar incorrectamente.
2. Instalación
sync/errgroup no está incluido en la biblioteca estándar de Go, pero forma parte del submódulo golang.org/x/sync. La instalación se realiza con un solo comando:
go get golang.org/x/sync/errgroup
Después de esto, puedes importar el paquete en tu código:
import "golang.org/x/sync/errgroup"
Asegúrate de tener instalado Go versión 1.13 o superior (se recomienda 1.18+ para soporte de genéricos, aunque errgroup no los utiliza). Si usas módulos (go.mod), el comando go get actualizará automáticamente el archivo go.sum.
3. Inicio rápido — ejemplo mínimo funcional
A continuación se muestra un ejemplo sencillo que ejecuta tres gorutinas, cada una de las cuales devuelve un error si el número proporcionado es negativo. Si al menos una gorutina devuelve un error, errgroup.Wait() lo devolverá.
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 // captura de variable
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
if n < 0 {
return fmt.Errorf("número negativo: %d", n)
}
fmt.Printf("Procesado %d\n", n)
return nil
})
}
if err := g.Wait(); err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("Todas las tareas se completaron con éxito")
}
}
Salida: Procesado 1, Procesado 3, Error: número negativo: -2. Observa que la gorutina con el número 3 podría no haberse ejecutado si el contexto se hubiera cancelado antes de su inicio; esto demuestra el mecanismo de detención temprana.
4. Métodos y funciones principales
A continuación se presentan todos los elementos clave de la API del paquete errgroup. Cada método incluye su firma, descripción y un ejemplo funcional.
4.1. WithContext
Firma: func WithContext(ctx context.Context) (*Group, context.Context)
Descripción: Crea un nuevo Group asociado a un contexto derivado. Este contexto se cancela automáticamente cuando la primera gorutina devuelve un error (distinto de nil). Es la función de entrada principal para usar errgroup.
Ejemplo:
g, ctx := errgroup.WithContext(context.Background())
// Ahora puedes usar g.Go() y ctx para cancelación
4.2. Go
Firma: func (g *Group) Go(f func() error)
Descripción: Ejecuta la función f en una nueva gorutina. Si f devuelve un error, el contexto del grupo se cancela automáticamente. El método es seguro para usar desde múltiples gorutinas.
Ejemplo:
g.Go(func() error {
// Simular trabajo
return nil
})
4.3. Wait
Firma: func (g *Group) Wait() error
Descripción: Bloquea la ejecución hasta que todas las gorutinas lanzadas con Go hayan finalizado. Devuelve el primer error no nilo que se haya producido, o nil si todas las gorutinas se completaron sin errores.
Ejemplo:
if err := g.Wait(); err != nil {
log.Fatalf("Error en el grupo: %v", err)
}
4.4. SetLimit (introducido en Go 1.20+)
Firma: func (g *Group) SetLimit(n int)
Descripción: Limita el número de gorutinas que se ejecutan simultáneamente. Si n es 0 o negativo, no hay límite. Esto es útil para controlar el uso de recursos como conexiones de red o memoria.
Ejemplo:
g.SetLimit(5) // Máximo 5 gorutinas concurrentes
for i := 0; i < 100; i++ {
i := i
g.Go(func() error {
// Procesar elemento i
return nil
})
}
4.5. TryGo (introducido en Go 1.20+)
Firma: func (g *Group) TryGo(f func() error) bool
Descripción: Intenta ejecutar la función f en una nueva gorutina. Devuelve true si la gorutina se inició correctamente, o false si se ha alcanzado el límite de gorutinas concurrentes establecido con SetLimit. No bloquea la ejecución.
Ejemplo:
if g.TryGo(func() error {
// Trabajo opcional
return nil
}) {
fmt.Println("Gorutina iniciada")
} else {
fmt.Println("Límite alcanzado, reintentar más tarde")
}