Go Concurrency & Worker Pool Patterns¶
1. Structured Concurrency with golang.org/x/sync/errgroup¶
Always bound goroutine lifecycles and propagate the first encountered error:
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
"time"
)
func FetchAllUserData(ctx context.Context, userIDs []string) error {
// Cancel sibling operations immediately if any one fails
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10) // Bounded concurrency limit
for _, id := range userIDs {
userID := id // Pin loop variable (prior to Go 1.22)
g.Go(func() error {
return fetchProfile(ctx, userID)
})
}
return g.Wait()
}
func fetchProfile(ctx context.Context, id string) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
fmt.Printf("Fetched user %s\n", id)
return nil
}
}
2. Goroutine Leak Prevention¶
- Never launch a goroutine without knowing how and when it terminates.
- Always check
ctx.Done()when reading/writing unbuffered channels or waiting on IO. - Always run the race detector in tests:
go test -race ./....