Mutex, RWMutex, WaitGroup, Once, atomic, errgroup.
errgroup — Concurrent Tasks with Shared Cancellation
golang.org/x/sync/errgroup is
a third-party stdlib (semi-official: same team, separate module). It wraps the
"launch N goroutines, wait for all, propagate the first error" pattern that
otherwise needs a WaitGroup, a chan error, a sync.Once, and a context — all
hand-wired.
If you write any real Go service, you will use errgroup.
Install
go get golang.org/x/sync/errgroup
It's outside the stdlib but maintained by the Go team and effectively standard.
The basic shape
import "golang.org/x/sync/errgroup"
var g errgroup.Group
for _, url := range urls {
url := url // (not needed in Go 1.22+, but harmless)
g.Go(func() error {
return fetch(url)
})
}
if err := g.Wait(); err != nil {
return err // the first non-nil error from any goroutine
}
g.Go(fn) runs fn in a goroutine. g.Wait() blocks until all goroutines have
finished and returns the first non-nil error.
With cancellation: errgroup.WithContext
The killer feature. If any goroutine returns an error, the shared context is cancelled — every other goroutine that respects the context bails out immediately:
Worker 2 fails after 100 ms, ctx cancels, workers 1 and 3 see ctx.Done() and
exit. g.Wait() returns worker 2's error. No worker is left dangling.
Limiting concurrency: SetLimit
For "do at most K concurrently":
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(8) // at most 8 concurrent g.Go calls
for _, item := range items {
item := item
g.Go(func() error {
return process(ctx, item)
})
}
return g.Wait()
SetLimit makes g.Go block until a slot is free. A simple, sturdy worker
pool with zero ceremony.
When NOT to use errgroup
- You need every error, not just the first → collect into a
[]errorand useerrors.Join. - You need values back, not just errors →
errgrouponly returns errors; pair with a results channel or a typed group likesourcegraph/conc. - It's a single goroutine →
errgroupis overkill; just call the function.