引入
goroutine 启动了,但多个 goroutine 之间怎么传递数据?共享变量加锁是一种办法,但 Go 更推荐用 channel——“不要通过共享内存来通信,而要通过通信来共享内存”。channel 就是 goroutine 之间传递数据的管道。
正文
定义
定义
channel 是
goroutine之间传递数据的管道,用chan关键字声明。数据从一端发送,从另一端接收,天然支持并发安全。channel 分为无缓冲(同步通信)和有缓冲(异步通信)两种。
语法
// 声明无缓冲 channel
ch := make(chan int)
// 声明有缓冲 channel(容量为 3)
ch := make(chan int, 3)
// 发送数据
ch <- value
// 接收数据
value := <-ch
// 关闭 channel
close(ch)例子
无缓冲 channel
ch := make(chan string)
go func() {
ch <- "hello"
}()
msg := <-ch
fmt.Println(msg) // hello无缓冲 channel 的发送和接收必须同时准备好,一方没到位另一方就会阻塞。像两个人交接东西,必须同时在场。
有缓冲 channel
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
// ch <- 4 // 缓冲区满了,会阻塞
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2有缓冲 channel 像一个队列,缓冲区没满时发送不阻塞,缓冲区空了时接收会阻塞。
goroutine 之间通信
func producer(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go producer(ch)
for v := range ch {
fmt.Println(v)
}
}一个 goroutine 生产数据发送到 channel,主 goroutine 用 for range 接收,close() 后循环自动结束。
关闭 channel
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
// 关闭后还能继续接收已有的数据
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
// 再接收会返回零值
fmt.Println(<-ch) // 0close(ch) 关闭 channel 后,不能再发送数据(会 panic),但可以继续接收已缓冲的数据。接收空 channel 会返回值类型的零值。
判断 channel 是否关闭
ch := make(chan int)
close(ch)
v, ok := <-ch
fmt.Println(v, ok) // 0 false用 v, ok := <-ch 的逗号 ok 模式,ok 为 false 表示 channel 已关闭且无数据。
单向 channel
// 只能发送
func send(ch chan<- int) {
ch <- 42
}
// 只能接收
func receive(ch <-chan int) {
fmt.Println(<-ch)
}
func main() {
ch := make(chan int)
go send(ch)
receive(ch)
}chan<- 是只发不收的 channel,<-chan 是只收不发的 channel。在函数参数中使用可以限制方向,让代码更安全。
channel 作为信号
done := make(chan struct{})
go func() {
fmt.Println("任务完成")
close(done)
}()
<-done
fmt.Println("收到完成信号")用空结构体 struct{} 的 channel 当信号量,不传数据,只用来通知完成。
常见写法
| 写法 | 说明 |
|---|---|
make(chan T) | 创建无缓冲 channel |
make(chan T, n) | 创建有缓冲 channel(容量 n) |
ch <- v | 发送数据 |
v := <-ch | 接收数据 |
close(ch) | 关闭 channel |
v, ok := <-ch | 接收并判断是否关闭 |
for v := range ch | 循环接收直到关闭 |
chan<- T | 只发送 channel |
<-chan T | 只接收 channel |
特点
- channel 是
goroutine之间传递数据的管道,并发安全 - 无缓冲 channel 发送和接收同步,双方必须同时就位
- 有缓冲 channel 缓冲区未满时发送不阻塞
close()后不能再发送,但能继续接收已有数据- 对未关闭的 channel
for range会永久阻塞 - 重复
close()一个 channel 会 panic - 向
nilchannel 发送或接收会永久阻塞 - 单向 channel(
chan<-/<-chan)用来限制数据流方向
理解
channel 就像一根水管,一头灌水(发送),一头出水(接收)。无缓冲的水管必须两头同时接着才能流通,有缓冲的水管能先存一点水再慢慢流。用 close() 告诉接收方”不会再有新数据了”,for range 就能自动停下来。channel 让 goroutine 之间的通信变得简单又安全。
引出
channel 一次只能等一个,如果要同时监听多个 channel 呢?接下来看 Go select,学习怎么在多个 channel 操作中选择一个执行。