Channels
Prerequisites: Goroutines
A channel is a typed pipe that goroutines use to send values to each other. Sending and receiving also synchronise the goroutines involved — the Go proverb is "don't communicate by sharing memory; share memory by communicating."
- Unbuffered (
make(chan int)): a send blocks until another goroutine receives, and vice versa. - Buffered (
make(chan int, 3)): sends only block when the buffer is full; receives only block when it is empty. close(ch)signals that no more values will be sent. Receivers can still drain buffered values; after that they get the zero value andok == false.for v := range chstops automatically once the channel is closed and empty.- Directional types document and enforce intent:
chan<- intcan only send,<-chan intcan only receive.
Syntax
ch := make(chan string) // unbuffered
buf := make(chan int, 2) // buffered, capacity 2
ch <- "hi" // send
msg := <-ch // receive
v, ok := <-ch // ok is false once ch is closed and drained
close(ch)
func producer(out chan<- int) {} // send-only parameter
func consumer(in <-chan int) {} // receive-only parameter
Example
package main
import "fmt"
func produce(n int, out chan<- int) {
for i := 1; i <= n; i++ {
out <- i
}
close(out) // the sender closes, never the receiver
}
func square(in <-chan int, out chan<- int) {
for v := range in {
out <- v * v
}
close(out)
}
func main() {
// Unbuffered: main blocks on the receive until the goroutine sends.
done := make(chan string)
go func() {
done <- "worker finished"
}()
fmt.Println(<-done)
// Buffered: two sends succeed without a waiting receiver.
buf := make(chan int, 2)
buf <- 10
buf <- 20
fmt.Println(len(buf), cap(buf))
fmt.Println(<-buf, <-buf)
// A pipeline: produce -> square -> main.
nums := make(chan int)
squares := make(chan int)
go produce(4, nums)
go square(nums, squares)
for s := range squares {
fmt.Println(s)
}
// nums is closed and empty now.
v, ok := <-nums
fmt.Println(v, ok)
}
Expected output
worker finished
2 2
10 20
1
4
9
16
0 false
The pipeline output is in order because each stage is a single goroutine and channels preserve the order of values sent on them.
Common mistakes
- Sending on an unbuffered channel when nothing will ever receive. If every
goroutine is blocked, the program crashes with
fatal error: all goroutines are asleep - deadlock!. - Forgetting to
closea channel that a receiver ranges over — therangewaits forever. - Closing a channel from the receiving side, closing it twice, or sending on a closed channel. The last two panic. Only the sender should close, and only when receivers need to know that no more values are coming.
- Using a buffered channel to "fix" a deadlock without understanding it. A buffer changes when blocking happens, not whether your design is correct.
Practice
Write lengths(words []string) <-chan int that starts a goroutine sending
the length of each word and then closes the channel. In main, range over
the result for []string{"go", "is", "fun"} and print the total.
Show solution
package main
import "fmt"
func lengths(words []string) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, w := range words {
out <- len(w)
}
}()
return out
}
func main() {
total := 0
for n := range lengths([]string{"go", "is", "fun"}) {
total += n
}
fmt.Println(total) // 7
}