Channels
A goroutine can compute a value, but the go statement discards the function's result. How does a cook hand a finished dish to a waiter? A kitchen has a serving hatch for that: the cook puts a plate on one side, and the waiter takes it from the other.
A value from a goroutine
In Go, that hatch is a channel. One goroutine sends values into it, and another receives them:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 6 * 7
}()
fmt.Println(<-ch) // 42
}Let's walk through it line by line:
ch := make(chan int)opens a hatch forintvalues.go func() { … }()starts the cook goroutine: it computes6 * 7and sends42into the channel.fmt.Println(<-ch)—main, playing the waiter, waits at the hatch, takes42and prints it.
The channel type
A channel is created with make: make(chan int) gives a channel of int values, and its type is chan int. The channel type sets the type of its values: sending a string into a chan int does not compile.
A channel is passed to a function as an ordinary parameter: func send(ch chan int). The copy of a channel refers to the same channel, as a copy of a map refers to the same table.
Send and receive
Two operations work with a channel. The arrow shows where the value goes.
Send
The statement ch <- v sends the value v into the channel. The arrow points toward the channel: the value goes into it.
Receive
The expression <-ch receives a value from the channel. The arrow comes out of the channel: the value comes out. The received value can be stored in a variable: v := <-ch.
An unbuffered channel
A channel created with make(chan int) is called unbuffered: it holds no values. Such a hatch has no shelf: the cook holds the plate until the waiter takes it. The two operations wait for each other:
- a send waits until another goroutine starts a receive;
- a receive waits for a send.
That is why main in the first example needs no WaitGroup: <-ch waits until the goroutine sends 42.
Press the button and watch the cook wait at the hatch until the waiter takes the soup.
The sender is blocked: an unbuffered channel stores nothing. The cook waits at the hatch until a receiver takes the soup.
Values from one sender arrive in the order it sent them:
ch := make(chan string)
go func() {
ch <- "one"
ch <- "two"
}()
fmt.Println(<-ch, <-ch) // one twoStep by step:
- The first receive takes
"one". - Until
mainhas taken the first plate, the cook cannot serve the second. - The second receive takes
"two", and the program printsone two.
This is where the hatch picture stops working: in a kitchen, the cook and the waiter are always different people. In Go, one and the same goroutine may send and receive a value. But with an unbuffered channel it would then wait for itself.
Deadlock
A goroutine waiting on a send or a receive waits until another goroutine performs the matching operation. If every goroutine of the program is waiting, none of them can move. This is a deadlock. The Go runtime notices it and stops the program with an error:
fatal error: all goroutines are asleep - deadlock!The compiler does not find this error: the build succeeds, and the program stops when it runs.
A nil channel
Interviewers like to ask what happens with a nil channel. The answer has three facts:
- a channel declared with
var ch chan intisnil: the hatch has not been opened yet; - a send into a
nilchannel and a receive from it wait forever; - if nobody else is working, the program stops with the same error.
That is why a channel is created with make.
Common mistake
A function that sends a value into a channel was called without go:
func send(ch chan int) {
ch <- 42
}
func main() {
ch := make(chan int)
send(ch) // ✗
fmt.Println(<-ch)
}Here is what happens:
sendruns insidemainitself and waits for a receiver on the linech <- 42.- The cook stands at the hatch with a plate, and the waiter is the cook.
- The receive on the next line never starts, so the program stops with
fatal error: all goroutines are asleep - deadlock!.
The fix is go send(ch): then the send waits in a separate goroutine, and main reaches the receive.
Key points
- A channel passes values between goroutines:
ch <- vsends,<-chreceives. - An unbuffered channel holds no values: a send waits for a receive, and a receive waits for a send.
- When every goroutine is waiting, that is a deadlock, and the program stops at run time.
- A send into a
nilchannel and a receive from it wait forever; create channels withmake.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.