引入

goroutine 启动了,但多个 goroutine 之间怎么传递数据?共享变量加锁是一种办法,但 Go 更推荐用 channel——“不要通过共享内存来通信,而要通过通信来共享内存”。channel 就是 goroutine 之间传递数据的管道。

正文

定义

定义

channelgoroutine 之间传递数据的管道,用 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,主 goroutinefor 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) // 0

close(ch) 关闭 channel 后,不能再发送数据(会 panic),但可以继续接收已缓冲的数据。接收空 channel 会返回值类型的零值。

判断 channel 是否关闭

ch := make(chan int)
close(ch)
 
v, ok := <-ch
fmt.Println(v, ok) // 0 false

v, ok := <-ch 的逗号 ok 模式,okfalse 表示 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
  • nil channel 发送或接收会永久阻塞
  • 单向 channel(chan<- / <-chan)用来限制数据流方向

理解

channel 就像一根水管,一头灌水(发送),一头出水(接收)。无缓冲的水管必须两头同时接着才能流通,有缓冲的水管能先存一点水再慢慢流。用 close() 告诉接收方”不会再有新数据了”,for range 就能自动停下来。channel 让 goroutine 之间的通信变得简单又安全。

引出

channel 一次只能等一个,如果要同时监听多个 channel 呢?接下来看 Go select,学习怎么在多个 channel 操作中选择一个执行。