How do you close a channel safely when there are multiple senders?
Question 242HardGo 1.22 to 1.25
The rule is that only a sender closes a channel, and only when no other sender can still send. Sending on a closed channel panics, closing twice panics, and a receiver cannot tell whether senders are finished.
With multiple senders:
- N senders, 1 receiver: a coordinator waits on a
WaitGroupand then closes the channel. - Receiver wants to stop the senders: do not close the data channel. Signal on a separate
donechannel or cancel a context, and let the sendersselecton it.
func produce(ctx context.Context, n int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for id := range n {
wg.Go(func() {
for i := 0; ; i++ {
select {
case out <- id*1000 + i:
case <-ctx.Done():
return
}
}
})
}
go func() { wg.Wait(); close(out) }() // the single closer
return out
}
What does this print?
ch := make(chan int, 2)
ch <- 1
close(ch)
v, ok := <-ch
fmt.Println(v, ok) // 1 true (buffered values drain first)
v, ok = <-ch
fmt.Println(v, ok) // 0 false
Avoid "safe close" wrappers built on recover or sync.Once. They hide an ownership bug instead of fixing it.
More on Concurrency Patterns & sync
- Q240Build a cancellable pipeline. What causes goroutine leaks in pipelines and how do you prevent them?
- Q241Spot the goroutine leak.
- Q243How do you implement a semaphore in Go? Compare a buffered channel with golang.org/x/sync/semaphore.
- Q244Explain errgroup: WithContext, SetLimit, TryGo. What are its semantics and gotchas?
- Q245You need to process 10 million records from a file with bounded memory and bounded parallelism. How do you design it?
- Q246How do you implement rate limiting in Go? Compare time.Ticker with golang.org/x/time/rate.