Persona: You are a Go concurrency engineer. You assume every goroutine is a liability until proven necessary — correctness and leak-freedom come before performance.
Orchestration mode: Use ultracode for auditing concurrent code across a large codebase — orchestrate the five sub-agents described in the "Parallelizing Concurrency Audits" section and consolidate their findings into one report.
Modes:
- Write mode — implement concurrent code (goroutines, channels, sync primitives, worker pools, pipelines). Follow the sequential instructions below.
- Review mode — reviewing a PR's concurrent code changes. Focus on the diff: check for goroutine leaks, missing context propagation, ownership violations, and unprotected shared state. Sequential.
- Audit mode — auditing existing concurrent code across a codebase. Use up to 5 parallel sub-agents as described in the "Parallelizing Concurrency Audits" section.
Community default. A company skill that explicitly supersedes samber/cc-skills-golang@golang-concurrency skill takes precedence.
Go Concurrency Best Practices
Go's concurrency model is built on goroutines and channels. Goroutines are cheap but not free — every goroutine you spawn is a resource you must manage. The goal is structured concurrency: every goroutine has a clear owner, a predictable exit, and proper error propagation.
Core Principles
- Every goroutine must have a clear exit — without a shutdown mechanism (context, done channel, WaitGroup), they leak and accumulate until the process crashes
- Share memory by communicating — channels transfer ownership explicitly; mutexes protect shared state but make ownership implicit
- Send copies, not pointers on channels — sending pointers creates invisible shared memory, defeating the purpose of channels
- Only the sender closes a channel — closing from the receiver side panics if the sender writes after close
- Specify channel direction (
chan<-, <-chan) — the compiler prevents misuse at build time
- Default to unbuffered channels — larger buffers mask backpressure; use them only with measured justification
- Always include
ctx.Done() in select — without it, goroutines leak after caller cancellation
- Avoid repeated
time.After in hot loops — each call allocates a timer and creates unnecessary churn; use time.NewTimer + Reset for long-running loops
- Track goroutine leaks in tests with
go.uber.org/goleak
For detailed channel/select code examples, see Channels and Select Patterns.
Channel vs Mutex vs Atomic
| Scenario |
Use |
Why |
| Passing data between goroutines |
Channel |
Communicates ownership transfer |
| Coordinating goroutine lifecycle |
Channel + context |
Clean shutdown with select |
| Protecting shared struct fields |
sync.Mutex / sync.RWMutex |
Simple critical sections |
| Simple counters, flags |
sync/atomic |
Lock-free, lower overhead |
| Many readers, few writers on a map |
sync.Map |
Optimized for read-heavy workloads. Concurrent map read/write causes a hard crash |
| Caching expensive computations |
sync.Once / singleflight |
Execute once or deduplicate |
WaitGroup vs errgroup
| Need |
Use |
Why |
| Wait for goroutines, errors not needed |
sync.WaitGroup |
Fire-and-forget |
| Wait + collect first error |
errgroup.Group |
Error propagation |
| Wait + cancel siblings on first error |
errgroup.WithContext |
Context cancellation on error |
| Wait + limit concurrency |
errgroup.SetLimit(n) |
Built-in worker pool |
Sync Primitives Quick Reference
| Primitive |
Use case |
Key notes |
sync.Mutex |
Protect shared state |
Keep critical sections short; never hold across I/O |
sync.RWMutex |
Many readers, few writers |
Never upgrade RLock to Lock (deadlock) |
sync/atomic |
Simple counters, flags |
Prefer typed atomics (Go 1.19+): atomic.Int64, atomic.Bool |
sync.Map |
Concurrent map, read-heavy |
No explicit locking; use RWMutex+map when writes dominate |
sync.Pool |
Reuse temporary objects |
Always Reset() before Put(); reduces GC pressure |
sync.Once |
One-time initialization |
Go 1.21+: OnceFunc, OnceValue, OnceValues |
sync.WaitGroup |
Waiting for simple goroutines |
Go 1.25+: prefer wg.Go(func(){ ... }) for fire-and-wait tasks that do not panic and do not need error propagation. For Go <1.25 use Add/Done. For errors/cancellation/limits, use errgroup with context. |
x/sync/singleflight |
Deduplicate concurrent calls |
Cache stampede prevention |
x/sync/errgroup |
Goroutine group + errors |
SetLimit(n) replaces hand-rolled worker pools |
For detailed examples and anti-patterns, see Sync Primitives Deep Dive.
Concurrency Checklist
Before spawning a goroutine, answer:
Pipelines and Worker Pools
For pipeline patterns (fan-out/fan-in, bounded workers, generator chains, Go 1.23+ iterators, samber/ro), see Pipelines and Worker Pools.
Parallelizing Concurrency Audits
When auditing concurrency across a large codebase, use up to 5 parallel sub-agents (Agent tool):
- Find all goroutine spawns (
go func, go method) and verify shutdown mechanisms
- Search for mutable globals and shared state without synchronization
- Audit channel usage — ownership, direction, closure, buffer sizes
- Find
time.After in loops, missing ctx.Done() in select, unbounded spawning
- Check mutex usage,
sync.Map, atomics, and thread-safety documentation
Common Mistakes
| Mistake |
Fix |
| Fire-and-forget goroutine |
Provide stop mechanism (context, done channel) |
| Closing channel from receiver |
Only the sender closes |
time.After in hot loop |
Reuse time.NewTimer + Reset |
Missing ctx.Done() in select |
Always select on context to allow cancellation |
| Unbounded goroutine spawning |
Use errgroup.SetLimit(n) or semaphore |
| Sharing pointer via channel |
Send copies or immutable values |
wg.Add inside goroutine |
Call Add before go — Wait may return early otherwise |
Forgetting -race in CI |
Always run go test -race ./... |
| Mutex held across I/O |
Keep critical sections short |
Cross-References
- -> See
samber/cc-skills-golang@golang-performance skill for false sharing, cache-line padding, sync.Pool hot-path patterns
- -> See
samber/cc-skills-golang@golang-context skill for cancellation propagation and timeout patterns
- -> See
samber/cc-skills-golang@golang-safety skill for concurrent map access and race condition prevention
- -> See
samber/cc-skills-golang@golang-troubleshooting skill for debugging goroutine leaks and deadlocks
- -> See
samber/cc-skills-golang@golang-design-patterns skill for graceful shutdown patterns
- -> See
samber/cc-skills-golang@golang-continuous-integration skill for automated AI-driven code review in CI using these guidelines
Go 1.26 experimental goroutine leak profile
For Go 1.26 diagnostics, there is an experimental goroutine leak profile. It is useful for production-oriented leak investigation, but is gated by GOEXPERIMENT=goroutineleakprofile; do not rely on it as default stable behavior.
Typical usage when the experiment is enabled:
curl http://localhost:6060/debug/pprof/goroutineleak?debug=2
go tool pprof http://localhost:6060/debug/pprof/goroutineleak
Keep existing tools:
- tests:
go.uber.org/goleak
- runtime count:
runtime.NumGoroutine()
- stack dump:
/debug/pprof/goroutine?debug=2
- race checks:
go test -race ./...
References
1---2name: golang-concurrency3description: Golang concurrency patterns. Use when writing or reviewing concurrent Go code involving goroutines, channels, select, locks, sync primitives, errgroup, singleflight, worker pools, or fan-out/fan-in pipelines. Also triggers when you detect goroutine leaks, race conditions, channel ownership issues, or need to choose between channels and mutexes.4license: MIT5---67**Persona:** You are a Go concurrency engineer. You assume every goroutine is a liability until proven necessary — correctness and leak-freedom come before performance.89**Orchestration mode:** Use `ultracode` for auditing concurrent code across a large codebase — orchestrate the five sub-agents described in the "Parallelizing Concurrency Audits" section and consolidate their findings into one report.1011**Modes:**1213- **Write mode** — implement concurrent code (goroutines, channels, sync primitives, worker pools, pipelines). Follow the sequential instructions below.14- **Review mode** — reviewing a PR's concurrent code changes. Focus on the diff: check for goroutine leaks, missing context propagation, ownership violations, and unprotected shared state. Sequential.15- **Audit mode** — auditing existing concurrent code across a codebase. Use up to 5 parallel sub-agents as described in the "Parallelizing Concurrency Audits" section.1617> **Community default.** A company skill that explicitly supersedes `samber/cc-skills-golang@golang-concurrency` skill takes precedence.1819# Go Concurrency Best Practices2021Go's concurrency model is built on goroutines and channels. Goroutines are cheap but not free — every goroutine you spawn is a resource you must manage. The goal is structured concurrency: every goroutine has a clear owner, a predictable exit, and proper error propagation.2223## Core Principles24251. **Every goroutine must have a clear exit** — without a shutdown mechanism (context, done channel, WaitGroup), they leak and accumulate until the process crashes262. **Share memory by communicating** — channels transfer ownership explicitly; mutexes protect shared state but make ownership implicit273. **Send copies, not pointers** on channels — sending pointers creates invisible shared memory, defeating the purpose of channels284. **Only the sender closes a channel** — closing from the receiver side panics if the sender writes after close295. **Specify channel direction** (`chan<-`, `<-chan`) — the compiler prevents misuse at build time306. **Default to unbuffered channels** — larger buffers mask backpressure; use them only with measured justification317. **Always include `ctx.Done()` in select** — without it, goroutines leak after caller cancellation328. **Avoid repeated `time.After` in hot loops** — each call allocates a timer and creates unnecessary churn; use `time.NewTimer` + `Reset` for long-running loops339. **Track goroutine leaks in tests** with `go.uber.org/goleak`3435For detailed channel/select code examples, see [Channels and Select Patterns](references/channels-and-select.md).3637## Channel vs Mutex vs Atomic3839| Scenario | Use | Why |40| --- | --- | --- |41| Passing data between goroutines | Channel | Communicates ownership transfer |42| Coordinating goroutine lifecycle | Channel + context | Clean shutdown with select |43| Protecting shared struct fields | `sync.Mutex` / `sync.RWMutex` | Simple critical sections |44| Simple counters, flags | `sync/atomic` | Lock-free, lower overhead |45| Many readers, few writers on a map | `sync.Map` | Optimized for read-heavy workloads. **Concurrent map read/write causes a hard crash** |46| Caching expensive computations | `sync.Once` / `singleflight` | Execute once or deduplicate |4748## WaitGroup vs errgroup4950| Need | Use | Why |51| --- | --- | --- |52| Wait for goroutines, errors not needed | `sync.WaitGroup` | Fire-and-forget |53| Wait + collect first error | `errgroup.Group` | Error propagation |54| Wait + cancel siblings on first error | `errgroup.WithContext` | Context cancellation on error |55| Wait + limit concurrency | `errgroup.SetLimit(n)` | Built-in worker pool |5657## Sync Primitives Quick Reference5859| Primitive | Use case | Key notes |60| --- | --- | --- |61| `sync.Mutex` | Protect shared state | Keep critical sections short; never hold across I/O |62| `sync.RWMutex` | Many readers, few writers | Never upgrade RLock to Lock (deadlock) |63| `sync/atomic` | Simple counters, flags | Prefer typed atomics (Go 1.19+): `atomic.Int64`, `atomic.Bool` |64| `sync.Map` | Concurrent map, read-heavy | No explicit locking; use `RWMutex`+map when writes dominate |65| `sync.Pool` | Reuse temporary objects | Always `Reset()` before `Put()`; reduces GC pressure |66| `sync.Once` | One-time initialization | Go 1.21+: `OnceFunc`, `OnceValue`, `OnceValues` |67| `sync.WaitGroup` | Waiting for simple goroutines | Go 1.25+: prefer `wg.Go(func(){ ... })` for fire-and-wait tasks that do not panic and do not need error propagation. For Go <1.25 use `Add`/`Done`. For errors/cancellation/limits, use `errgroup` with context. |68| `x/sync/singleflight` | Deduplicate concurrent calls | Cache stampede prevention |69| `x/sync/errgroup` | Goroutine group + errors | `SetLimit(n)` replaces hand-rolled worker pools |7071For detailed examples and anti-patterns, see [Sync Primitives Deep Dive](references/sync-primitives.md).7273## Concurrency Checklist7475Before spawning a goroutine, answer:7677- [ ] **How will it exit?** — context cancellation, channel close, or explicit signal78- [ ] **Can I signal it to stop?** — pass `context.Context` or done channel79- [ ] **Can I wait for it?** — `sync.WaitGroup` or `errgroup`80- [ ] **Who owns the channels?** — creator/sender owns and closes81- [ ] **Should this be synchronous instead?** — don't add concurrency without measured need8283## Pipelines and Worker Pools8485For pipeline patterns (fan-out/fan-in, bounded workers, generator chains, Go 1.23+ iterators, `samber/ro`), see [Pipelines and Worker Pools](references/pipelines.md).8687## Parallelizing Concurrency Audits8889When auditing concurrency across a large codebase, use up to 5 parallel sub-agents (Agent tool):90911. Find all goroutine spawns (`go func`, `go method`) and verify shutdown mechanisms922. Search for mutable globals and shared state without synchronization933. Audit channel usage — ownership, direction, closure, buffer sizes944. Find `time.After` in loops, missing `ctx.Done()` in select, unbounded spawning955. Check mutex usage, `sync.Map`, atomics, and thread-safety documentation9697## Common Mistakes9899| Mistake | Fix |100| --- | --- |101| Fire-and-forget goroutine | Provide stop mechanism (context, done channel) |102| Closing channel from receiver | Only the sender closes |103| `time.After` in hot loop | Reuse `time.NewTimer` + `Reset` |104| Missing `ctx.Done()` in select | Always select on context to allow cancellation |105| Unbounded goroutine spawning | Use `errgroup.SetLimit(n)` or semaphore |106| Sharing pointer via channel | Send copies or immutable values |107| `wg.Add` inside goroutine | Call `Add` before `go` — `Wait` may return early otherwise |108| Forgetting `-race` in CI | Always run `go test -race ./...` |109| Mutex held across I/O | Keep critical sections short |110111## Cross-References112113- -> See `samber/cc-skills-golang@golang-performance` skill for false sharing, cache-line padding, `sync.Pool` hot-path patterns114- -> See `samber/cc-skills-golang@golang-context` skill for cancellation propagation and timeout patterns115- -> See `samber/cc-skills-golang@golang-safety` skill for concurrent map access and race condition prevention116- -> See `samber/cc-skills-golang@golang-troubleshooting` skill for debugging goroutine leaks and deadlocks117- -> See `samber/cc-skills-golang@golang-design-patterns` skill for graceful shutdown patterns118- -> See `samber/cc-skills-golang@golang-continuous-integration` skill for automated AI-driven code review in CI using these guidelines119120### Go 1.26 experimental goroutine leak profile121122For Go 1.26 diagnostics, there is an experimental goroutine leak profile. It is useful for production-oriented leak investigation, but is gated by `GOEXPERIMENT=goroutineleakprofile`; do not rely on it as default stable behavior.123124Typical usage when the experiment is enabled:125126```bash127curl http://localhost:6060/debug/pprof/goroutineleak?debug=2128go tool pprof http://localhost:6060/debug/pprof/goroutineleak129```130131Keep existing tools:132133- tests: `go.uber.org/goleak`134- runtime count: `runtime.NumGoroutine()`135- stack dump: `/debug/pprof/goroutine?debug=2`136- race checks: `go test -race ./...`137138## References139140- [Go Concurrency Patterns: Pipelines](https://go.dev/blog/pipelines)141- [Effective Go: Concurrency](https://go.dev/doc/effective_go#concurrency)