Golang 从入门到放弃 -0x0D
channel、close、range、select 和超时控制,补齐 Go 并发通信模型。
上一章我们把 goroutine 开起来了,但只会开还不够。多个 goroutine 之间怎么传数据、怎么协调节奏、怎么优雅地超时退出,这一章轮到 channel 和 select 上场。
channel 是什么
可以先把 channel 理解成一根带规矩的管道。一个 goroutine 往里塞数据,另一个 goroutine 从里面拿数据。
ch := make(chan int)发送数据:
ch <- 10接收数据:
value := <-ch箭头方向很形象,数据朝哪边流,你就往哪边看。
无缓冲 channel
默认创建出来的是无缓冲 channel。它的特点是:发送方和接收方要对上节奏,否则谁都别想继续走。
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "hello"
}()
msg := <-ch
fmt.Println(msg)
}这里如果没有 goroutine,主线程自己给自己发再自己收,就很容易死锁给你看。
有缓冲 channel
有时候你希望这根管道先存几条消息,那就可以加缓冲区。
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)容量是 2,就表示在没人接收的情况下,最多能先塞两个值。再多就堵住。
关闭 channel
当发送方确定后面不会再发数据了,可以关闭 channel。
close(ch)关闭后不能再往里发送,但接收方还可以把缓冲区里剩下的值拿完。
遍历 channel 时,range 和 close 是绝配。
ch := make(chan int)
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch)
}()
for value := range ch {
fmt.Println(value)
}select:多路等待
当你要同时等多个 channel,或者想加一个超时控制,select 就特别好用。
select {
case msg := <-ch1:
fmt.Println("来自 ch1:", msg)
case msg := <-ch2:
fmt.Println("来自 ch2:", msg)
default:
fmt.Println("都没消息")
}如果没有 default,select 会阻塞,直到某个分支能继续执行。加了 default,就会变成“没等到就先走”。
超时控制
配合 time.After,你可以很容易给等待过程加个超时。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch <- "done"
}()
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(time.Second):
fmt.Println("超时了")
}
}这个模式在调用外部服务、等待异步结果时非常常见。你总不能让一个请求无限等下去,不然等着等着,连用户都忘了自己点过什么了。
小结
channel 和 select 是 Go 并发模型里非常核心的两块:
- channel 用来在 goroutine 之间传数据。
- 无缓冲强调同步,有缓冲强调削峰。
close + range是经典组合。select让你同时等待多个事件,还能顺手做超时控制。
下一章开始做点更像样的东西,我们用 net/http 写一个最简单的 Web 服务。