You range over a channel by using a for loop with the range keyword, which automatically iterates through received values until the channel is closed. The loop terminates immediately when the channel is closed, so you must ensure the sender explicitly closes the channel to avoid a deadlock.
Here is a practical example showing how to receive values from a channel and detect when it closes:
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 3)
// Send values
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch) // Critical: must close to signal completion
}()
// Range over the channel
for val := range ch {
fmt.Printf("Received: %d\n", val)
}
fmt.Println("Channel closed, loop finished.")
}
If you need to handle errors or distinguish between a received value and a closed channel without blocking indefinitely, you can use the two-value form of the receive operation inside a standard for loop instead of range. This gives you more control over the loop logic:
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hello"
ch <- "World"
close(ch)
}()
for {
val, ok := <-ch
if !ok {
fmt.Println("Channel is closed")
break
}
fmt.Println(val)
}
}
Key points to remember:
- Always close the channel from the sender side when no more values will be sent. If you range over an open channel that never receives a close signal, your program will hang forever.
- Do not close a channel that is currently being written to by multiple goroutines unless you are certain no more writes will occur, as closing an already closed channel causes a panic.
- Range is safe for concurrent reads, but if you need to break early based on a condition, use the explicit
val, ok := <-chpattern instead ofrange.