Go Concurrency
Prioritize correctness, bounded resource use, and understandable ownership. Concurrency is justified by a concrete need such as overlapping I/O, parallel computation, independent lifecycle work, or coordination—not by stylistic preference.
Inspect before changing
- Read repository instructions,
go.mod, nearby synchronization conventions, tests, and shutdown wiring.
- Trace each relevant goroutine from creation to completion: owner, inputs, outputs, cancellation, errors, panic behavior, and who waits.
- Identify shared mutable state and the synchronization that establishes a happens-before relationship for every access.
- Define expected concurrency, queue limits, overload behavior, ordering, and partial-result semantics.
- Preserve existing lifecycle and API contracts unless the requested task requires changing them.
Core invariants
- Every spawned goroutine needs a defined owner and completion condition. Expose cancellation or waiting when the surrounding lifecycle must control or observe it.
- Every channel needs a clear protocol: who sends, who receives, whether it closes, and who is allowed to close it. Usually the component that knows no more sends can occur closes it; receivers do not close merely to stop a sender.
- A send of a pointer is not inherently unsafe. The real requirement is explicit ownership, immutability, confinement, or synchronization for the pointed-to data.
- Add a cancellation case when a potentially blocking operation belongs to cancellable work. A
ctx.Done() arm is not useful in a select whose operation must complete regardless of caller cancellation.
- Concurrency and queues should be bounded when input can outpace service. Define what happens at capacity: block, reject, drop, coalesce, or spill.
- Do not hold a lock while calling unknown code or performing slow I/O unless the protected invariant truly requires it and the consequence is documented.
- Closing a channel is a broadcast about future sends, not a general resource cleanup mechanism. Nil channels block forever; a receive from a closed channel yields the zero value and
ok == false.
For channel protocols and cancellation details, read channels and select.
Choose the simplest primitive
| Need |
Usual starting point |
| Guard related fields or a multi-step invariant |
sync.Mutex |
| Coordinate ownership or stream values |
Channel |
| Wait for tasks that do not return errors |
sync.WaitGroup |
| Propagate errors/cancel siblings and the project already uses it |
errgroup |
| Independent numeric flag or counter with a precise atomic invariant |
Typed sync/atomic value |
| One-time initialization |
sync.Once or a supported convenience wrapper |
| Specialized concurrent map access |
sync.Map, only after its documented use cases fit |
Do not choose RWMutex, atomics, sync.Map, or sync.Pool from a generic performance claim. Their benefit and complexity depend on access patterns; profile or benchmark hot paths. See synchronization primitives.
Pipelines and worker pools
Use a pipeline only when stages have useful independent lifecycles or concurrency. For an in-process transformation that is naturally sequential, a loop or iterator is simpler. When concurrency is warranted, ensure cancellation reaches blocked receives and sends, output is closed exactly once, worker count is bounded, and error semantics are explicit. See pipelines and worker pools.
Verification
Exercise the repository's focused tests first. When scope and runtime make it practical, run the race detector on affected packages:
go test -race ./path/to/affected/...
The race detector finds executed data races, not deadlocks, leaks, logical races, or untested paths. Add deterministic tests around shutdown, cancellation, capacity, and error paths; avoid sleep-based timing where a channel or barrier can synchronize the test.
In review, report the concrete interleaving and consequence. “Could race” is incomplete without identifying the unsynchronized accesses or violated protocol.
1---2name: golang-concurrency3description: Design or review concurrent Go code involving goroutines, channels, locks, atomics, worker pools, or race and leak symptoms. Use when ownership, synchronization, cancellation, backpressure, or shutdown is central; not for ordinary sequential code that merely accepts a context.4license: MIT5---67# Go Concurrency89Prioritize correctness, bounded resource use, and understandable ownership. Concurrency is justified by a concrete need such as overlapping I/O, parallel computation, independent lifecycle work, or coordination—not by stylistic preference.1011## Inspect before changing12131. Read repository instructions, `go.mod`, nearby synchronization conventions, tests, and shutdown wiring.142. Trace each relevant goroutine from creation to completion: owner, inputs, outputs, cancellation, errors, panic behavior, and who waits.153. Identify shared mutable state and the synchronization that establishes a happens-before relationship for every access.164. Define expected concurrency, queue limits, overload behavior, ordering, and partial-result semantics.175. Preserve existing lifecycle and API contracts unless the requested task requires changing them.1819## Core invariants2021- Every spawned goroutine needs a defined owner and completion condition. Expose cancellation or waiting when the surrounding lifecycle must control or observe it.22- Every channel needs a clear protocol: who sends, who receives, whether it closes, and who is allowed to close it. Usually the component that knows no more sends can occur closes it; receivers do not close merely to stop a sender.23- A send of a pointer is not inherently unsafe. The real requirement is explicit ownership, immutability, confinement, or synchronization for the pointed-to data.24- Add a cancellation case when a potentially blocking operation belongs to cancellable work. A `ctx.Done()` arm is not useful in a select whose operation must complete regardless of caller cancellation.25- Concurrency and queues should be bounded when input can outpace service. Define what happens at capacity: block, reject, drop, coalesce, or spill.26- Do not hold a lock while calling unknown code or performing slow I/O unless the protected invariant truly requires it and the consequence is documented.27- Closing a channel is a broadcast about future sends, not a general resource cleanup mechanism. Nil channels block forever; a receive from a closed channel yields the zero value and `ok == false`.2829For channel protocols and cancellation details, read [channels and select](references/channels-and-select.md).3031## Choose the simplest primitive3233| Need | Usual starting point |34| --- | --- |35| Guard related fields or a multi-step invariant | `sync.Mutex` |36| Coordinate ownership or stream values | Channel |37| Wait for tasks that do not return errors | `sync.WaitGroup` |38| Propagate errors/cancel siblings and the project already uses it | `errgroup` |39| Independent numeric flag or counter with a precise atomic invariant | Typed `sync/atomic` value |40| One-time initialization | `sync.Once` or a supported convenience wrapper |41| Specialized concurrent map access | `sync.Map`, only after its documented use cases fit |4243Do not choose `RWMutex`, atomics, `sync.Map`, or `sync.Pool` from a generic performance claim. Their benefit and complexity depend on access patterns; profile or benchmark hot paths. See [synchronization primitives](references/sync-primitives.md).4445## Pipelines and worker pools4647Use a pipeline only when stages have useful independent lifecycles or concurrency. For an in-process transformation that is naturally sequential, a loop or iterator is simpler. When concurrency is warranted, ensure cancellation reaches blocked receives and sends, output is closed exactly once, worker count is bounded, and error semantics are explicit. See [pipelines and worker pools](references/pipelines.md).4849## Verification5051Exercise the repository's focused tests first. When scope and runtime make it practical, run the race detector on affected packages:5253```bash54go test -race ./path/to/affected/...55```5657The race detector finds executed data races, not deadlocks, leaks, logical races, or untested paths. Add deterministic tests around shutdown, cancellation, capacity, and error paths; avoid sleep-based timing where a channel or barrier can synchronize the test.5859In review, report the concrete interleaving and consequence. “Could race” is incomplete without identifying the unsynchronized accesses or violated protocol.