Use `select` with `ctx.Done()` on every channel send: ```go func producer(ctx context.Context, ch chan<- Result) { for { result, err := doWork(ctx) if err != nil { return } select { case ch <- result: // sent successfully case <-ctx.Done(): return // context cancelled, exit cleanly } } } ``` **On the race question:** When both cases are ready simultaneously, Go's `select` picks one **uniformly at random**. So yes, there's a ~50% chance the send fires even when the context is already cancelled. This is fine — one extra item in the channel is harmless. The important thing is the goroutine **will** exit on the next loop iteration. **Buffered channels:** With a buffered channel, the send can succeed without a reader. The goroutine might send one more item after cancellation, but it won't block — it'll hit `ctx.Done()` on the next iteration and exit. **The idiomatic pattern is:** 1. Always `select` between the channel op and `ctx.Done()` 2. The consumer should also `select` on `ctx.Done()` when reading 3. Close the channel from the producer side when done (signals consumer) 4. Never close from the consumer side For fan-out (multiple producers, one consumer), use `sync.WaitGroup` to close the channel after all producers exit.
a53ba7aa-b3c0-4919-9059-93040245fe91
Use select with ctx.Done() on every channel send:
func producer(ctx context.Context, ch chan<- Result) {
for {
result, err := doWork(ctx)
if err != nil {
return
}
select {
case ch <- result:
// sent successfully
case <-ctx.Done():
return // context cancelled, exit cleanly
}
}
}On the race question: When both cases are ready simultaneously, Go's select picks one uniformly at random. So yes, there's a ~50% chance the send fires even when the context is already cancelled. This is fine — one extra item in the channel is harmless. The important thing is the goroutine will exit on the next loop iteration.
Buffered channels: With a buffered channel, the send can succeed without a reader. The goroutine might send one more item after cancellation, but it won't block — it'll hit ctx.Done() on the next iteration and exit.
The idiomatic pattern is:
- Always
selectbetween the channel op andctx.Done() - The consumer should also
selectonctx.Done()when reading - Close the channel from the producer side when done (signals consumer)
- Never close from the consumer side
For fan-out (multiple producers, one consumer), use sync.WaitGroup to close the channel after all producers exit.