Patterns: concurrent Go · 3 MIN READ
Cancellation: propagate an exit path through every handoff
Make a producer stop when its consumer no longer needs results.
Make a producer stop when its consumer no longer needs results. Cancellation is a cooperative signal, not a forced goroutine kill. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Concurrent Go pattern · A MAP OF THE IDEA
- Caller owns and cancels the context
- Blocking handoff selects on Done
- Producer closes output and the caller waits
Useful when
- A consumer can stop before a stream ends
- Work needs deadlines or shutdown coordination
Watch out
- Cancellation is cooperative and can race with delivery
- Unsupported blocking APIs need separate lifecycle design
Implementation & operation cost: Medium
Fit depends on the task
Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.
The design decision
Cancellation is a cooperative signal, not a forced goroutine kill. Blocking channel operations must observe it, and the caller still needs a way to wait for completion. Carry context through the operation instead of storing request contexts in long-lived structs.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"context"
"fmt"
)
func Numbers(ctx context.Context) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for i := 1; ; i++ {
select {
case out <- i:
case <-ctx.Done():
return
}
}
}()
return out
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
out := Numbers(ctx)
first := <-out
cancel()
for range out {
} // Wait until the producer closes its channel.
fmt.Println(first, ctx.Err())
}
// Output:
// 1 context canceled
The producer can leave a blocked send through ctx.Done and closes its own output on return. main cancels after the first value, then drains until completion. A racing send may already be ready when cancellation arrives, so cancellation does not promise zero additional values.
Tradeoffs and the simpler alternative
Cancel on all completion paths to release timers and derived contexts. Do not replace completion waiting with time.Sleep. Real I/O adapters must support deadlines or cancellation themselves; passing a context does not interrupt an arbitrary blocking function.
TAKE THIS WITH YOU
Requires blocking work to observe the cancellation signal. Cancel on all completion paths to release timers and derived contexts. Do not replace completion waiting with time.Sleep. Real I/O adapters must support deadlines or cancellation themselves; passing a context does not interrupt an arbitrary blocking function.
Check your understanding
Which statement best describes Cancellation: propagate an exit path through every handoff?
Go to the source
Progress stays in this browser. No account needed.