go-concurrency — Go concurrency
Deterministic backstop: go test -race ./..., go vet ./... (catches copylocks, lost cancel),
and go.uber.org/goleak. The race detector is the source of truth — run it before reasoning.
The runtime also ships an experimental goroutineleak profile in runtime/pprof (Go 1.26)
that reports leaked goroutines — a toolchain-native complement to goleak for leak hunts (on Go
1.26, enable it with GOEXPERIMENT=goroutineleakprofile at build time). The implementation is
production-ready; the experiment flag is only about API feedback, and it costs nothing unless in use.
On Go 1.27 the profile is generally available with no build flag — read it via
pprof.Lookup("goroutineleak") or the net/http/pprof endpoint /debug/pprof/goroutineleak. Source:
https://go.dev/doc/go1.27; https://pkg.go.dev/runtime/pprof.
Rules
- Every goroutine needs a known lifetime. Tie it to a
contextor a done signal. A goroutine that can block forever on a channel send/recv after its reader has returned is a leak — the single most common Go concurrency bug. - Prefer
wg.Go(func(){ … })(Go 1.25) overwg.Add(1); go func(){ defer wg.Done() }()— it removes the Add/Done-mismatch footgun. - For groups that can fail, use
golang.org/x/sync/errgroup: the first non-nil error cancels the group's derived context;g.SetLimit(n)bounds concurrency. Don't hand-roll error+WaitGroup plumbing. - Typed atomics (Go 1.19):
atomic.Int64,atomic.Bool,atomic.Pointer[T]— notatomic.AddInt64(&x, …)on a bare int. Typed forms are self-documenting and can't be read non-atomically by accident. - Context discipline: pass
ctx context.Contextas the first parameter; never store it in a struct (containedctx); don't reach forcontext.Background()deep in a call stack — thread the caller's ctx. HTTP/SQL/RPC calls must carry it (noctx), and set a client timeout. - Make cancellation say why.
ctx.Err()only ever reportscontext.CanceledorDeadlineExceeded, which is useless for diagnosis when several budgets nest. Usecontext.WithCancelCause+cancel(err)(Go 1.20) and readcontext.Cause(ctx), orWithTimeoutCause/WithDeadlineCause(1.21) so the expiring layer names itself.errors.Is(err, context.Canceled)keeps working — the cause rides alongside, it doesn't replaceErr(). - Work that must outlive the request:
context.WithoutCancel(ctx)(Go 1.21) drops cancellation but keeps the values (trace, auth, request ID) — reach for it instead ofcontext.Background(), which throws those away. Give the derived context its own timeout, and tie it to a shutdown path; "outlives the request" must not mean "outlives the process silently". context.AfterFunc(ctx, f)(Go 1.21) instead of a goroutine whose only job is toselectonctx.Done()and clean up; the returnedstopunregisters it if the work finished first.- Don't copy
sync.Mutex/sync.WaitGroupby value (go vetcopylocks). Guard shared maps — a concurrent map write panics;-racecatches it. - Channels: close on the send side, never the receive side; a
nilchannel blocks forever (useful for disabling aselectarm, a bug everywhere else). - Prefer synchronous APIs. Return the result; let the caller decide to run it in a goroutine. A function that spawns internally and hands back a channel — or takes a completion callback — imposes its concurrency model on every caller and hides the goroutine's lifetime, which is exactly where leaks come from.
- Cleanup is explicit, never finalized. Release resources in
Close/defer.runtime.AddCleanup(Go 1.24, preferred over the olderruntime.SetFinalizer: multiple cleanups per object, works on interior pointers, no leak on reference cycles) is a backstop for OS/native handles only — a cleanup may never run, so no correctness may depend on it. - Testing time/concurrency: use
testing/synctest(stable since Go 1.25) — fake clock + deterministic scheduling. Seego-testing.
Sources
- synctest — https://go.dev/blog/synctest; Go 1.25/1.26 release notes — https://go.dev/doc/go1.26
goroutineleakprofile (Go 1.26, experimental) — https://pkg.go.dev/runtime/pprofcontext(Cause,WithoutCancel,AfterFunc,WithTimeoutCause) — https://pkg.go.dev/context;runtime.AddCleanup— https://pkg.go.dev/runtime#AddCleanup- Code Review Comments (Goroutine Lifetimes, Contexts, Synchronous Functions) — https://go.dev/wiki/CodeReviewComments
- Uber Go Style Guide (Concurrency) — https://github.com/uber-go/guide
Decomposition inspired by samber/cc-skills-golang (MIT © 2026 Samuel Berthe); rules grounded in the sources above.