When a goroutine has finished sending on a channel, call Go’s built-in `close(c)` to mark that channel closed for writing. Receivers can still read values already sent. Once those are exhausted, a receive returns the channel’s element type’s zero value; a send is no longer possible, and closure cannot be undone. section 7.2.3 section 9.5.2
Signal that a task is done
A worker can notify its caller with `c := make(chan struct{}, 1)`. In a deferred function, it sends `struct{}{}` and then calls `close(c)`; the caller waits with `_ = <-c`. An empty structure carries no task data—the receive is the notification. Placing the notification in a defer keeps it tied to the worker’s return. section 7.2.3
Closure is especially useful when several goroutines need the same quit signal. Sending one value would wake one receiver; sending enough values would require knowing how many workers remain. Instead, close the quit channel when the solution is found. Each worker can then receive from that closed channel and stop. section 9.5.2
Listing 9.26 explore.go: Stop listening to new messages
func (s *Solver) listenToBranches() {
wg := sync.WaitGroup{}
defer wg.Wait()
for {
select {
case <-s.quit: #1
log.Println("the treasure has been found, stopping worker")
return
case p := <-s.pathsToExplore: #2
wg.Add(1)
go func(p *path) {
defer wg.Done()
s.explore(p)
}(p)
}
}
}
The branch listener includes `<-s.quit` in a `select` and returns when the quit channel becomes readable. It also waits for the exploration goroutines it started. section 9.5.2
Keep the receiving code aligned with what closure means:
- Use `value, ok := <-c` when the value matters: `ok` distinguishes a sent value from the zero value returned after closure. A quit-only channel needs no such check if nothing is sent on it. section 9.5.2
- A `range` over a channel waits for messages while the channel remains open. Closing the channel lets that listening pattern finish, but it does not by itself track whether goroutines started from those messages have finished. section 9.3.4
- When a worker may either publish another job or quit, put the receive and send in the same `select`. A separate nonblocking quit check followed by a send leaves a gap in which the quit channel can close. section 9.5.2
select {
case <-s.quit:
log.Printf("I'm an unlucky branch, someone else found the treasure, I
give up at position %v.", pos)
return
case s.pathsToExplore <- branch:
//continue execution after the select block
}
This `select` places the quit receive beside the branch send, avoiding the separate-check-then-send pattern. section 9.5.2
Do not confuse built-in `close(c)` with a file’s `f.Close()` method. The former ends channel sends and provides a completion signal; the latter releases file resources and is commonly deferred immediately after a successful `os.Open`. section 7.2.3 section 3.1.2


