Channels
A channel is a typed pipe that goroutines use to send values to each other. Sending and receiving also synchronise the goroutines involved, so a channel both moves data and coordinates timing. Go's motto sums it up: "Don't communicate by sharing memory; share memory by communicating." This lesson covers unbuffered and buffered channels, closing and ranging, directional channel types, and the mistakes that cause deadlocks.
Sending and receiving
Create a channel with make(chan T). ch <- v sends a value; v := <-ch receives one:
package main import "fmt" func square(n int, out chan int) { out <- n * n // send } func main() { results := make(chan int) go square(4, results) go square(9, results) a := <-results // receive: waits until a value arrives b := <-results fmt.Println(a + b) }
97
On an unbuffered channel, a send waits until another goroutine receives, and a receive waits until another goroutine sends. The two goroutines meet at that moment, which is why main doesn't need a WaitGroup here: it can't get past the receives until both results have arrived. (Which square arrives first can vary, but the sum is always the same.)
Closing and ranging
A sender can close a channel to say "no more values are coming". A for … range loop over a channel receives until the channel is closed and empty:
package main import "fmt" func generate(n int, out chan<- int) { for i := 1; i <= n; i++ { out <- i * 10 } close(out) // tells the receiver we're done } func main() { ch := make(chan int) go generate(4, ch) for v := range ch { fmt.Print(v, " ") } fmt.Println() v, ok := <-ch // receiving from a closed channel: zero value, ok == false fmt.Println(v, ok) }
10 20 30 40 0 false
Rules for closing: only the sender should close a channel, never the receiver; closing is only needed when receivers must know there's no more data (as with range); and sending on a closed channel, or closing one twice, panics.
Buffered channels
make(chan T, n) creates a channel with room for n values. Sends only block when the buffer is full, and receives only block when it's empty:
package main import "fmt" func main() { jobs := make(chan string, 3) jobs <- "resize" jobs <- "compress" jobs <- "upload" // no receiver yet, but there's room: no blocking fmt.Println(len(jobs), cap(jobs)) close(jobs) for j := range jobs { fmt.Println("doing", j) } }
3 3 doing resize doing compress doing upload
A buffer smooths out bursts between a fast producer and a slower consumer, or lets a fixed number of goroutines report results without waiting for the receiver. Don't add buffers just to make a deadlock disappear — that usually hides a design problem.
Directional channel types
A function parameter can be restricted to sending (chan<- T) or receiving (<-chan T). The compiler then stops the function from using the channel the wrong way, and the signature documents the data flow:
package main import ( "fmt" "strings" ) func producer(words []string) <-chan string { // returns a receive-only channel out := make(chan string) go func() { defer close(out) for _, w := range words { out <- w } }() return out } func upper(in <-chan string) <-chan string { out := make(chan string) go func() { defer close(out) for w := range in { out <- strings.ToUpper(w) } }() return out } func main() { for w := range upper(producer([]string{"pipes", "and", "filters"})) { fmt.Println(w) } }
PIPES AND FILTERS
This is a small pipeline: each stage is a goroutine that receives from one channel and sends to the next. The pattern of a function that starts a goroutine and returns its output channel is very common.
Deadlock
If every goroutine is blocked waiting on a channel, nothing can ever proceed. The runtime detects when all goroutines are stuck and stops the program:
package main import "fmt" func main() { ch := make(chan int) ch <- 1 // blocks forever: no other goroutine will ever receive fmt.Println(<-ch) }
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: … exit status 2
Typical causes: sending on an unbuffered channel in the same goroutine that should receive, forgetting to close a channel that someone is ranging over, or receiving more values than are sent. When only some goroutines are stuck, the runtime can't tell — those goroutines simply leak.
nil channels
The zero value of a channel type is nil. Sending to or receiving from a nil channel blocks forever. That sounds useless, but it's handy in select statements for switching a case off (next lesson). Always create channels with make before use.
Channel operations summary
| Operation | nil channel | Open channel | Closed channel |
|---|---|---|---|
Send ch <- v | Blocks forever | Blocks until received (or buffer has room) | Panics |
Receive <-ch | Blocks forever | Blocks until a value is available | Remaining values, then zero value with ok == false |
close(ch) | Panics | Closes it | Panics |
Try it yourself
Start one goroutine per word that sends back the word's length. Receive exactly as many results as you started, and add them up.
total letters: 25
select.