Channel Synchronization
Use channels as done signals to wait for goroutines to finish before continuing.
Channels can synchronize execution across goroutines. A goroutine signals it is done by sending a value (or closing) a channel - the main goroutine blocks on a receive until the signal arrives.
Launch a goroutine that does some work and signals completion via a done channel. The main goroutine blocks on <-done until the worker sends.
package main
import (
"fmt"
"time"
)
func worker(done chan bool) {
fmt.Println("working...")
time.Sleep(time.Second)
fmt.Println("done")
done <- true
}
func main() {
done := make(chan bool)
go worker(done)
<-done // block until worker signals
}When one goroutine needs to signal many others at once (called fan-out), close the channel instead of sending N values. A close broadcasts to all receivers.
done := make(chan struct{})
for i := 0; i < 3; i++ {
go func(id int) {
fmt.Printf("worker %d started\n", id)
<-done // each worker waits for the close
fmt.Printf("worker %d shutting down\n", id)
}(i)
}
time.Sleep(100 * time.Millisecond)
close(done) // broadcast to all three goroutines
time.Sleep(100 * time.Millisecond)For joining N goroutines each doing independent work, collecting results via a channel can replace sync.WaitGroup.
results := make(chan int, 5) // buffered so goroutines don't block
for i := 1; i <= 5; i++ {
go func(n int) {
results <- n * n
}(i)
}
sum := 0
for i := 0; i < 5; i++ {
sum += <-results
}
fmt.Println("sum of squares:", sum) // 55In production
The done channel pattern (close a chan struct{} to broadcast cancellation to multiple goroutines) predates the context package and is still common in library code. Prefer context.Context in new application code - it composes cancellation, deadlines, and values in one type that the whole standard library understands. chan struct{} uses zero memory per item and signals intent clearly: this channel carries no data, only timing.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free