带缓冲的 channel 和 close
上一课的 channel 里没有地方放值:发送的一方要等到有人来收。make 的时候多给一个数,channel 里就有了几个空位,这叫带缓冲的 channel。有空位时,发送放下值就走,不用等人来收:
1package main2 3import "fmt"4 5func main() {6 orders := make(chan string, 3)7 orders <- "咖啡"8 orders <- "三明治"9 orders <- "沙拉"10 fmt.Println(<-orders)11 fmt.Println(<-orders)12 fmt.Println(<-orders)13}
咖啡 三明治 沙拉
这里一个 goroutine 都没开,main 自己发三次、收三次,也没有死锁:三个值先存在 channel 的空位里。取出来的顺序和放进去的一样,先放的先取,像排队。点单步,看 orders 画成三个格子,一个个填上,又一个个空出来。
len 和 cap
cap(ch) 是一共有几个空位,len(ch) 是现在存着几个值:
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
上一课那种不带缓冲的 channel,cap 是 0:一个值也存不下,所以发送要等接收。
满了还是要等
空位用完了,发送照样要等,等到有人取走一个、空出位置来。没人来取,就是死锁:
1package main2 3import "fmt"4 5func main() {6 orders := make(chan string, 3)7 orders <- "咖啡"8 orders <- "三明治"9 orders <- "沙拉"10 orders <- "蛋糕"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 2发生了什么: 死锁:所有 goroutine 都在等,谁也动不了(比如往没人收的 channel 发,或者 Wait 等不到 Done)。
第 4 次发送时三个格子都满了,main 停在第 10 行等空位,可没有别的 goroutine 会来取:fatal error: all goroutines are asleep - deadlock!。
有人在取,就不会卡死,只是发得快的一方要停下来等。厨房一口气做五道菜,出菜口只放得下两盘,服务员一盘一盘端:
1package main2 3import "fmt"4 5func main() {6 counter := make(chan string, 2)7 go func() {8 for _, dish := range []string{"汤", "沙拉", "牛排", "米饭", "甜点"} {9 counter <- dish10 }11 }()12 for range 5 {13 fmt.Println("端走", <-counter)14 }15}
端走 汤 端走 沙拉 端走 牛排 端走 米饭 端走 甜点
点单步,看厨房那条泳道:前两道菜放进格子就接着做下一道;格子满了,它就变成「卡在发送」,等服务员端走一盘才放得进去。
close:不会再发了
发送的一方发完了,可以 close(ch) 告诉接收的一方:不会再有新的值了。关了以后,里面剩下的值照样能收;收完了再收,会马上得到零值。用 v, ok := <-ch 能分清是哪一种:
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 是 true:这是真的发过来的值。ok 是 false:channel 已经关了,也已经空了,v 只是零值 0。
for range:收到关闭为止
for v := range ch 一个个地收,channel 关了而且收完了,循环就结束。这是收「不知道有几个」的值最常用的写法:
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)20}
合计 100
点单步,看 main 和 sendPrices 一递一接;close(out) 以后,prices 标成关了,main 的 for range 收完最后一个值就走出循环。猜一猜,要是忘了 close 呢?
这段程序会怎样?
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("收完了")18}
三个值都收到了,可 channel 一直没关,for range 不知道已经没有了,还在等第四个。sendPrices 已经结束,没有谁会再发,于是死锁:all goroutines are asleep。
往关了的 channel 发
关了的 channel 能收,不能再发。发了就是 panic:
1package main2 3import "fmt"4 5func main() {6 orders := make(chan string, 3)7 orders <- "咖啡"8 close(orders)9 orders <- "蛋糕"10 fmt.Println(len(orders))11}
panic: send on closed channel
goroutine 1 [running]:
main.main(...)
/tmp/sandbox/prog.go:9 +0x1d
exit status 2发生了什么: channel 已经 close 了,不能再往里发。
panic: send on closed channel:往关了的 channel 发送。同一个 channel 关两次也是 panic(close of closed channel)。所以规矩是:谁发送,谁来关,而且发完最后一个再关。接收的一方不要去关,它不知道发送的一方还发不发。不需要 range 的 channel 也可以不关,用完了 Go 会自己回收。
一个厨师按单做菜
把这一课的东西放在一起:main 把订单放进一个带缓冲的 channel,放完就关;厨师用 range 一单一单地做,做完把一共用的分钟数发回来:
1package main2 3import "fmt"4 5func cook(orders <-chan int, done chan<- int) {6 minutes := 07 for m := range orders {8 fmt.Println("做一道菜,用", m, "分钟")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("一共", <-done, "分钟")23}
做一道菜,用 5 分钟 做一道菜,用 12 分钟 做一道菜,用 8 分钟 一共 25 分钟
orders 有 10 个空位,main 三单一放就去关,不用等厨师;厨师收完三单、看到关了,range 结束,把总数发进 done。cook 的参数写成单向的:只从 orders 收,只往 done 发。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
过一遍 channel 的总价
✓写好 total:建一个正好放得下所有价格的带缓冲 channel,把价格都放进去,关掉,再用 for range 取出来加起来。程序会显示两个购物车的总价。
50 130
1package main2 3import "fmt"4 5func total(prices []int) int {6 sum := 07 // 建 channel,把价格放进去、关掉、再取出来加8 return sum9}10 11func main() {12 fmt.Println(total([]int{12, 30, 8}))13 fmt.Println(total([]int{99, 1, 25, 5}))14}
修好它:等不到头的 range
✓evens 把 2 到 limit 之间的偶数发出来,sumEvens 收下来加在一起。可程序死锁了。改好它,让它显示 30 和 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}
厨师一共忙了多久
✓写好 cook:用 for range 从 orders 收每道菜要的分钟数,加起来,收完以后发进 done。程序会算两个晚上的单子。
25 51
1package main2 3import "fmt"4 5func cook(orders <-chan int, done chan<- int) {6 // 收完所有订单,把总分钟数发进 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}
这一课学会了
make(chan T, n)建一个有 n 个空位的 channel:有空位时发送不用等,满了才等;取出的顺序和放进的一样。len(ch)是存着几个,cap(ch)是一共几个空位;不带缓冲的 channelcap是 0。close(ch):不会再发了。剩下的值还能收,收完再收得到零值。v, ok := <-ch:ok是false说明 channel 关了、空了。for v := range ch收到关闭为止;忘了close就会一直等,变成死锁。- 往关了的 channel 发是 panic
send on closed channel;谁发送谁来关。 - 单步时看 channel 的格子怎么填满、发送的一方什么时候「卡在发送」。