$npx -y skills add samber/cc-skills-golang --skill golang-concurrencyGolang 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
| 1 | **Persona:** You are a Go concurrency engineer. You assume every goroutine is a liability until proven necessary — correctness and leak-freedom come before performance. |
| 2 | |
| 3 | **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. |
| 4 | |
| 5 | **Modes:** |
| 6 | |
| 7 | - **Write mode** — implement concurrent code (goroutines, channels, sync primitives, worker pools, pipelines). Follow the sequential instructions below. |
| 8 | - **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. |
| 9 | - **Audit mode** — auditing existing concurrent code across a codebase. Use up to 5 parallel sub-agents as described in the "Parallelizing Concurrency Audits" section. |
| 10 | |
| 11 | > **Community default.** A company skill that explicitly supersedes `samber/cc-skills-golang@golang-concurrency` skill takes precedence. |
| 12 | |
| 13 | # Go Concurrency Best Practices |
| 14 | |
| 15 | 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. |
| 16 | |
| 17 | ## Core Principles |
| 18 | |
| 19 | 1. **Every goroutine must have a clear exit** — without a shutdown mechanism (context, done channel, WaitGroup), they leak and accumulate until the process crashes |
| 20 | 2. **Share memory by communicating** — channels transfer ownership explicitly; mutexes protect shared state but make ownership implicit |
| 21 | 3. **Send copies, not pointers** on channels — sending pointers creates invisible shared memory, defeating the purpose of channels |
| 22 | 4. **Only the sender closes a channel** — closing from the receiver side panics if the sender writes after close |
| 23 | 5. **Specify channel direction** (`chan<-`, `<-chan`) — the compiler prevents misuse at build time |
| 24 | 6. **Default to unbuffered channels** — larger buffers mask backpressure; use them only with measured justification |
| 25 | 7. **Always include `ctx.Done()` in select** — without it, goroutines leak after caller cancellation |
| 26 | 8. **Avoid repeated `time.After` in hot loops** — each call allocates a timer and creates unnecessary churn; use `time.NewTimer` + `Reset` for long-running loops |
| 27 | 9. **Track goroutine leaks in tests** with `go.uber.org/goleak` |
| 28 | |
| 29 | For detailed channel/select code examples, see [Channels and Select Patterns](references/channels-and-select.md). |
| 30 | |
| 31 | ## Channel vs Mutex vs Atomic |
| 32 | |
| 33 | | Scenario | Use | Why | |
| 34 | | --- | --- | --- | |
| 35 | | Passing data between goroutines | Channel | Communicates ownership transfer | |
| 36 | | Coordinating goroutine lifecycle | Channel + context | Clean shutdown with select | |
| 37 | | Protecting shared struct fields | `sync.Mutex` / `sync.RWMutex` | Simple critical sections | |
| 38 | | Simple counters, flags | `sync/atomic` | Lock-free, lower overhead | |
| 39 | | Many readers, few writers on a map | `sync.Map` | Optimized for read-heavy workloads. **Concurrent map read/write causes a hard crash** | |
| 40 | | Caching expensive computations | `sync.Once` / `singleflight` | Execute once or deduplicate | |
| 41 | |
| 42 | ## WaitGroup vs errgroup |
| 43 | |
| 44 | | Need | Use | Why | |
| 45 | | --- | --- | --- | |
| 46 | | Wait for goroutines, errors not needed | `sync.WaitGroup` | Fire-and-forget | |
| 47 | | Wait + collect first error | `errgroup.Group` | Error propagation | |
| 48 | | Wait + cancel siblings on first error | `errgroup.WithContext` | Context cancellation on error | |
| 49 | | Wait + limit concurrency | `errgroup.SetLimit(n)` | Built-in worker pool | |
| 50 | |
| 51 | ## Sync Primitives Quick Reference |
| 52 | |
| 53 | | Primitive | Use case | Key notes | |
| 54 | | --- | --- | --- | |
| 55 | | `sync.Mutex` | Protect shared state | Keep critical sections short; never hold across I/O | |
| 56 | | `sync.RWMutex` | Many readers, few writers | Never upgrade RLock to Lock (deadlock) | |
| 57 | | `sync/atomic` | Simple counters, flags | Prefer typed atomics (Go 1.19+): `atomic.Int64`, `atomic.Bool` | |
| 58 | | `sync.Map` | Concurrent map, read-heavy | No explicit locking; use `RWMutex`+map when writes dominate | |
| 59 | | `sync.Pool` | Reuse temporary objects |