How do you safely close a channel that might be closed by multiple goroutines?
Question 189HardGo 1.22 to 1.25
First, restructure the code if you can, because multiple closers usually means ownership isn't clear. When you genuinely need a "stop" signal that several parties may trigger, wrap the close in sync.Once:
type Stopper struct {
once sync.Once
ch chan struct{}
}
func NewStopper() *Stopper { return &Stopper{ch: make(chan struct{})} }
func (s *Stopper) Stop() { s.once.Do(func() { close(s.ch) }) }
func (s *Stopper) Done() <-chan struct{} { return s.ch }
context.cancelCtx uses the same idea internally: under a mutex it checks whether it is already cancelled and closes its lazily created done channel only the first time, so cancel() is safe to call repeatedly.
Anti-patterns interviewers want you to reject:
defer func(){ recover() }()aroundclose. It works, but it hides design bugs and is racy.- Checking "is it closed?" with a non-blocking receive before closing. This is a TOCTOU race: another goroutine can close it between the check and your close.
Only close a signal channel from multiple places. Never close a data channel that other goroutines are still sending on.
More on Channels & select
- Q187Explain the comma-ok idiom on channel receive. When is it needed and when is
for rangebetter? - Q188Who should close a channel, and why? What are the rules?
- Q190How does
selectchoose among multiple ready cases? Is it fair? - Q191How do you implement a priority select in Go?
- Q192Why is a nil channel useful in a
select? Show an example. - Q193What does this print? (select with a closed channel and a nil channel)