Buffers, close and range
In the previous lesson the serving hatch had no shelf: the cook held the plate until the waiter took it. That is why a goroutine cannot send a value into an unbuffered channel and then receive it itself. Now let's put a shelf for several plates at the hatch. The cook puts dishes on it and moves on while there is room.
A channel's buffer
That shelf is the channel's buffer, and its size is the second argument of make. A send into such a channel waits only when the buffer is full:
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch, <-ch) // 1 2
}Let's walk through the example line by line:
make(chan int, 2)gives a shelf for two values.ch <- 1andch <- 2put1and2on it and do not wait: there is room.fmt.Println(<-ch, <-ch)—mainitself takes them in the same order and prints1 2.
A buffer changes the waiting rules:
- a send waits only when the buffer is full;
- a receive takes the oldest value and waits only when the buffer is empty.
A third send into this channel before a receive would stop the program with a deadlock: the shelf is full, and there is no receiver.
With a buffer or without
The two kinds of channels make different promises.
A buffered channel
A buffer smooths out a difference in speed: the sender puts in several values and goes on without waiting for the receiver.
An unbuffered channel
An unbuffered channel gives a different promise: when a send has finished, the value has already been received.
Closing a channel
How does the waiter learn that no more dishes will come? The cook hangs an "Orders finished" sign on the hatch. In Go, that is the call close(ch): it tells receivers that no more values will come. After the close, receiving works like this:
- values already in the buffer can still be received;
- when a closed channel is empty, a receive does not wait and returns the zero value right away.
A second variable ok tells it apart from a zero that was sent, as when reading from a map:
ch := make(chan int, 1)
ch <- 5
close(ch)
v, ok := <-ch
fmt.Println(v, ok) // 5 true
v, ok = <-ch
fmt.Println(v, ok) // 0 falseLet's go through both receives:
- The first receive takes the
5that was on the shelf, andokistrue. - The second receive finds an empty shelf and the sign: it returns
0andfalsewithout waiting.
The sender closes the channel when it has sent everything. A channel needs closing only when the receiver must learn that no more values will come.
Panics on a closed channel
This is where the sign picture stops working. In a kitchen you can hang the sign and still put out one more plate. In Go, a closed channel does not forgive that:
- a send into a closed channel panics with
send on closed channel; - a second
closepanics withclose of closed channel.
Interviewers like to ask this. The answer: a send into a closed channel is a panic, while a receive from a closed, empty channel gives the zero value at once with ok == false.
range over a channel
The loop for v := range ch receives values one by one and ends when the channel is closed and empty. This is how the receiver learns that the sender has finished:
ch := make(chan int)
go func() {
for i := 1; i <= 3; i++ {
ch <- i * 10
}
close(ch)
}()
sum := 0
for v := range ch {
sum += v
}
fmt.Println(sum) // 60Let's see what happens here:
- The goroutine sends
10,20and30. - Then it hangs the sign:
close(ch). - The loop in
mainreceives three values and adds them up. rangedoes not receive a zero value after the close: the loop ends right after the last value sent.
So the sum is exactly 60.
Common mistake
A channel was read with a range loop, but nobody closed it:
ch := make(chan int, 2)
ch <- 1
ch <- 2
for v := range ch { // ✗
fmt.Println(v)
}fatal error: all goroutines are asleep - deadlock!Here is what happens:
- The loop prints
1and2and then waits for the next value. - There is no sign, and nobody is left to send a value.
- The runtime stops the program.
The fix is close(ch) after the last send.
Key points
- A buffer holds values: a send waits only when it is full, and a receive only when it is empty.
close(ch)closes a channel; buffered values can still be read, and then a receive gives the zero value at once withok == false.- A send into a closed channel and a second
closeboth panic. for v := range chends when the channel is closed and empty; withoutclose, the loop waits forever after the last value.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.