When I first started working with Go’s concurrency model, channels seemed mysterious. I knew they were a way for goroutines to talk to each other, but the finer points of how they communicate and when to close them were still fuzzy. After experimenting with a simple example, I now have a much clearer mental model.
1. Channels Are Pipelines for Communication
A channel in Go is like a conveyor belt between goroutines.
- A sender puts values onto the belt.
- A receiver takes values off the belt.
You can think of it as:
ch := make(chan int) // a channel for int values
Now ch
is ready to carry int
data from one goroutine to another.
2. Sender Channels vs Receiver Channels
In Go, you can explicitly mark a function’s parameter as a send-only or receive-only channel:
-
chan<- int
→ send-only channel -
<-chan int
→ receive-only channel
This makes the intent clear and prevents accidental misuse.
Example:
func producer(ch chan<- int) {
ch <- 42 // send only
}
3. Data Channels vs Signaling Channels
I’ve come to see two main roles for channels:
- Data Channels : carry actual information (numbers, strings, structs, etc.) between goroutines.
-
Signaling Channels : used only to say “I’m done” or “you can proceed,” often carrying
struct{}
(an empty struct) to avoid extra memory cost.
Example of a signaling channel:
done := make(chan struct{})
go func() {
// do some work
done <- struct{}{}
}()
<-done // wait for signal
4. Why Closing a Channel Matters
If the receiver is in a loop, it needs to know when there’s nothing more to read. Closing a channel is the sender’s way of saying:
"I’m done sending. Don’t wait for more."
For example:
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // tell receiver no more data
}()
for v := range ch {
fmt.Println(v)
}
Without close(ch)
, the range
loop would wait forever.
5. The Rule of Responsibility
The sender is responsible for closing the channel ; never the receiver.
Closing from the receiver side can cause a panic if the sender tries to send afterward.
6. Key Takeaways
- A channel is like a pipe. It can carry data or just a signal.
- Use
chan<-
and<-chan
to make sender/receiver intent clear. - Close a channel when you know no more data will be sent, especially for looping receivers.
- The sender owns the close operation.
Go’s channels aren’t just for sending data. They’re also about coordination. Thinking in terms of data channels vs signaling channels has made it much easier for me to decide when and how to close them.
Top comments (0)