A channel lets Go goroutines exchange values or signals. Use one when a goroutine needs to communicate data, an error, or completion—not merely because code runs concurrently. A channel has a message type, is created with make, and uses <- to send and receive. section 7.2.3 section 7.5.1
Choose the buffer with the sender and receiver in mind:
- With make(chan int), a send waits for a corresponding receive. A receive with no available message also waits. section 7.2.3 section 9.3.4
- With make(chan int, 10), sends can fill a ten-value buffer before a full buffer blocks further sends. Values are received in the order sent. section 7.2.3
- Close a channel when no more messages will be sent. Receivers can still read from a closed channel. section 7.2.3
c := make(chan struct{}, 1) #1
go func(doneChan chan <- struct{}) {
defer func() { #2
log.Println("done")
doneChan <- struct{}{}
close(doneChan) #3
} ()
// run task
}(c) #4
_ = <- c #5
A buffered chan struct{} carries a completion signal without meaningful data. The caller receives from it to wait for the goroutine; the sender's channel parameter permits sends only. section 7.2.3 section 7.6
A blocked maze solver
Suppose a solver sends its first path before it starts listening for paths. If its channel is nil, the program deadlocks; initializing an unbuffered channel still leaves that first send waiting for a receiver. A one-value buffer lets the solver publish the initial path and then start listening. Later, a listener that waits forever after exploration ends needs a separate stopping condition. section 9.3.4
In a concurrent maze solver, explorers send new paths to a listener that starts more goroutines. A separate quit channel can signal that a solution was found. Use a for-select loop to receive from both channels; when multiple cases are ready, Go chooses one at random. A sync.WaitGroup can then wait for launched explorers to finish. section 9.3.2 section 9.5.2
Listing 9.23 explore_internal_test.go: Testing the explore function
func TestSolver_explore(t *testing.T) {
tests := map[string]struct {
inputImage string #1
wantSize int #2
}{
"cross": {
inputImage: "testdata/explore_cross.png",
wantSize: 2,
},
// ...
}
for name, tt := range tests {
name, tt := name, tt
t.Run(name, func(t *testing.T) {
t.Parallel() #3
maze, err := openMaze(tt.inputImage) #4
require.NoError(t, err)
s := &Solver{
maze: maze,
palette: defaultPalette(),
pathsToExplore: make(chan *path, 3), #5
}
s.explore(&path{at: image.Point{0, 2}}) #6
assert.Equal(t,
tt.wantSize, len(s.pathsToExplore)) #7
})
}
}
To test an explorer without running its listener, give the channel enough buffer space for every expected branch, then inspect the number of published paths. Avoid making the test depend on an unspecified neighbor order. section 9.3.6
A channel need not be the answer to every synchronization problem: sync.WaitGroup waits for goroutines when errors need no special handling, while errgroup.Group supports waiting for errors. Reserve channels for communication into or out of goroutines. section 7.5.1 section 7.6









