Channels
| Make and send | |
| Receive and close | |
| Range until closed | |
| Buffered channels |
Make and send
Channels move values between goroutines safely, with no shared memory involved. make creates a channel for one element type. The send operator pushes a value in, and a receive pulls it out. An unbuffered send waits until someone receives, which keeps the two sides in step.
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "ping"
}()
fmt.Println(<-ch)
}
ping
The sender blocks until main receives, so nothing is lost and nothing races. Swap the order and the program still works. This handshake is the heartbeat of Go concurrency.
Receive and close
Receivers need to know when no more values will come. The sender closes the channel to say it is done, and each receive reports ok as false past that point. Check that flag to stop cleanly. Only the sender ever closes, exactly once.
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch)
}()
for {
v, ok := <-ch
if !ok {
break
}
fmt.Println(v)
}
}
1
2
3
Sending on a closed channel panics, so ownership of close must be clear. Receiving from a closed channel is safe and yields zero values. The range form below hides the flag check entirely.
Range until closed
range over a channel reads values until the channel closes, then exits the loop by itself. The shape mirrors a slice loop and reads well. Forgetting close here hangs the loop forever, which is the most common channel bug. Pair every ranging consumer with a closing producer.
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
fmt.Println("done")
}
1
2
3
done
Buffered channels
Buffered channels hold a few values without a waiting receiver. make takes a capacity, and sends block only when the buffer is full. Small buffers smooth out bursts between fast producers and slow consumers. Zero extra goroutines are needed for the example below.
package main
import "fmt"
func main() {
ch := make(chan string, 2)
ch <- "a"
ch <- "b"
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(len(ch), cap(ch))
}
a
b
0 2
Both sends succeed at once because capacity is two. A third send would block until a receive frees a slot. Buffers tune timing, while close still signals the end of data.
Next: select and sync
Article author: Arthur Isaev