Buffered channels and close
The channels in the last lesson have no room for a value: a sender waits until someone comes to receive. Give make one more number and the channel gets that many slots. That's a buffered channel. While there's a free slot, a send drops the value off and moves on, without waiting for a receiver:
1package main2 3import "fmt"4 5func main() {6 orders := make(chan string, 3)7 orders <- "coffee"8 orders <- "sandwich"9 orders <- "salad"10 fmt.Println(<-orders)11 fmt.Println(<-orders)12 fmt.Println(<-orders)13}
coffee sandwich salad
There isn't a single goroutine here besides main, which sends three times and receives three times, and there's no deadlock: the three values wait in the channel's slots. They come out in the order they went in, first in first out, like a queue. Press Step and see orders drawn as three slots that fill up one by one, then empty one by one.
len and cap
cap(ch) is how many slots there are, and len(ch) is how many values are in them right now:
1package main2 3import "fmt"4 5func main() {6 queue := make(chan int, 5)7 queue <- 1018 queue <- 1029 fmt.Println(len(queue), cap(queue))10 <-queue11 fmt.Println(len(queue), cap(queue))12 plain := make(chan int)13 fmt.Println(len(plain), cap(plain))14}
2 5 1 5 0 0
An unbuffered channel, like the ones in the last lesson, has a cap of 0: it can't hold a single value, which is why a send waits for a receive.
Full means waiting
Once the slots are used up, a send waits again, until someone takes a value out and frees a slot. If nobody ever does, it's a deadlock:
1package main2 3import "fmt"4 5func main() {6 orders := make(chan string, 3)7 orders <- "coffee"8 orders <- "sandwich"9 orders <- "salad"10 orders <- "cake"11 fmt.Println(<-orders)12}
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main(...)
/tmp/sandbox/prog.go:10 +0x1d
exit status 2What happened: Every goroutine is waiting (on a channel, a lock or a WaitGroup) and none can go on: a deadlock.
At the fourth send all three slots are full. main stops at line 10 to wait for a slot, but no other goroutine will ever take a value out: fatal error: all goroutines are asleep - deadlock!.
With someone taking values out, nothing gets stuck for good; the faster side just has to pause now and then. The kitchen makes five dishes in a row, the pass only holds two plates, and the waiter carries them out one at a time:
1package main2 3import "fmt"4 5func main() {6 counter := make(chan string, 2)7 go func() {8 for _, dish := range []string{"soup", "salad", "steak", "rice", "dessert"} {9 counter <- dish10 }11 }()12 for range 5 {13 fmt.Println("Served", <-counter)14 }15}
Served soup Served salad Served steak Served rice Served dessert
Press Step and watch the kitchen's lane: the first two dishes go into the slots and it moves straight on to the next. Once the slots are full it turns to "stuck sending" and waits until the waiter takes a plate and frees a slot.
close: no more values
When the sender has sent everything, it can call close(ch) to tell the receivers: no new values are coming. After a close, the values still inside can be received as usual; once they're gone, a receive gets the zero value right away. v, ok := <-ch tells you which is which:
1package main2 3import "fmt"4 5func main() {6 ch := make(chan int, 2)7 ch <- 108 ch <- 209 close(ch)10 v, ok := <-ch11 fmt.Println(v, ok)12 v, ok = <-ch13 fmt.Println(v, ok)14 v, ok = <-ch15 fmt.Println(v, ok)16}
10 true 20 true 0 false
ok is true: this value was really sent. ok is false: the channel is closed and empty, and v is just the zero value 0.
for range: receive until closed
for v := range ch receives the values one by one, and the loop ends when the channel is closed and empty. It's the usual way to receive values when you don't know how many are coming:
1package main2 3import "fmt"4 5func sendPrices(out chan int) {6 for _, p := range []int{12, 30, 8, 50} {7 out <- p8 }9 close(out)10}11 12func main() {13 prices := make(chan int)14 go sendPrices(prices)15 total := 016 for p := range prices {17 total += p18 }19 fmt.Println("Total", total)20}
Total 100
Press Step and watch main and sendPrices hand values across one at a time. After close(out), prices is marked closed, and main's for range leaves the loop once it has the last value. Guess: what if you forget the close?
What happens?
1package main2 3import "fmt"4 5func sendPrices(out chan int) {6 for _, p := range []int{12, 30, 8} {7 out <- p8 }9}10 11func main() {12 prices := make(chan int)13 go sendPrices(prices)14 for p := range prices {15 fmt.Println(p)16 }17 fmt.Println("All received")18}
All three values arrive, but the channel is never closed, so for range can't know there are no more and waits for a fourth. sendPrices has already ended and nobody will send again, so it's a deadlock: all goroutines are asleep.
Sending on a closed channel
You can receive from a closed channel, but not send on it. Try it and you get a panic:
1package main2 3import "fmt"4 5func main() {6 orders := make(chan string, 3)7 orders <- "coffee"8 close(orders)9 orders <- "cake"10 fmt.Println(len(orders))11}
panic: send on closed channel
goroutine 1 [running]:
main.main(...)
/tmp/sandbox/prog.go:9 +0x1d
exit status 2What happened: Nothing can be sent on a channel once it is closed.
panic: send on closed channel: a send on a channel that's already closed. Closing the same channel twice is a panic too (close of closed channel). So the rule is: the sender closes, after its last send. A receiver shouldn't close, because it can't know whether the sender is done. A channel nobody ranges over doesn't need closing at all; Go cleans it up when it's no longer used.
A cook working through orders
Putting this lesson together: main puts the orders into a buffered channel and closes it when it's done; the cook goes through them with range, and when they're all done, sends back the total minutes spent:
1package main2 3import "fmt"4 5func cook(orders <-chan int, done chan<- int) {6 minutes := 07 for m := range orders {8 fmt.Println("Cooking a dish:", m, "minutes")9 minutes += m10 }11 done <- minutes12}13 14func main() {15 orders := make(chan int, 10)16 done := make(chan int)17 go cook(orders, done)18 for _, m := range []int{5, 12, 8} {19 orders <- m20 }21 close(orders)22 fmt.Println("Total:", <-done, "minutes")23}
Cooking a dish: 5 minutes Cooking a dish: 12 minutes Cooking a dish: 8 minutes Total: 25 minutes
orders has 10 slots, so main drops off all three orders and closes it without waiting for the cook. The cook takes the three orders, sees the channel closed, leaves the range, and sends the total into done. cook's parameters are one-way: it only receives from orders and only sends on done.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
A total that goes through a channel
✓Finish total: make a buffered channel with exactly enough room for all the prices, put them all in, close it, then take them out with for range and add them up. The program shows the totals of two carts.
50 130
1package main2 3import "fmt"4 5func total(prices []int) int {6 sum := 07 // make the channel, put the prices in, close it, then add them up8 return sum9}10 11func main() {12 fmt.Println(total([]int{12, 30, 8}))13 fmt.Println(total([]int{99, 1, 25, 5}))14}
Fix it: the range that never ends
✓evens sends the even numbers from 2 up to limit, and sumEvens adds them up. But the program deadlocks. Fix it so it shows 30 and 12.
30 12
1package main2 3import "fmt"4 5func evens(limit int, out chan int) {6 for n := 2; n <= limit; n += 2 {7 out <- n8 }9}10 11func sumEvens(limit int) int {12 ch := make(chan int)13 go evens(limit, ch)14 sum := 015 for n := range ch {16 sum += n17 }18 return sum19}20 21func main() {22 fmt.Println(sumEvens(10))23 fmt.Println(sumEvens(7))24}
How long the cook was busy
✓Finish cook: use for range to receive each dish's minutes from orders, add them up, and when they're all in, send the total into done. The program works out two evenings of orders.
25 51
1package main2 3import "fmt"4 5func cook(orders <-chan int, done chan<- int) {6 // receive all the orders, then send the total minutes into done7 done <- 08}9 10func busyMinutes(minutes []int) int {11 orders := make(chan int, len(minutes))12 done := make(chan int)13 go cook(orders, done)14 for _, m := range minutes {15 orders <- m16 }17 close(orders)18 return <-done19}20 21func main() {22 fmt.Println(busyMinutes([]int{5, 12, 8}))23 fmt.Println(busyMinutes([]int{20, 15, 10, 6}))24}
What you learned
make(chan T, n)makes a channel with n slots: sends don't wait while there's room, only when it's full; values come out in the order they went in.len(ch)is how many values are waiting,cap(ch)how many slots there are; an unbuffered channel hascap0.close(ch): no more values. What's left can still be received; after that a receive gets the zero value.v, ok := <-ch:okisfalsewhen the channel is closed and empty.for v := range chreceives until the channel is closed; forget thecloseand it waits forever, a deadlock.- Sending on a closed channel panics with
send on closed channel; the sender closes. - Step shows a channel's slots filling up, and when a sender is "stuck sending".