在 Golang 中,channel 也是一种引用数据类型。还记得我们学过哪些引用类型吗?

所以 channel 作为参数,函数内对 channel 的修改,会直接影响到函数外面的变量。现在我们把上一节的 pipeline 进行改写,封装成函数的形式。

1. 封装 counter 和 squarer

package main

import "fmt"

// out 参数是传出参数
func counter(out chan int) {
for x := 0; x < 100; x++ {
out <- x
}
close(out)
}

// out 参数是传出参数,in 是传入参数
func squarer(out, in chan int) {
for x := range in {
out <- x * x
}
close(out)
}

func main() {
naturals := make(chan int)
squares := make(chan int)

// 启动两个协程
go counter(naturals)
go squarer(squares, naturals)

for x := range

naturals 和 squares 把三个协程串联起来,使得代码更加容易理解。上面的代码没有任何问题,但是……接下来才是本文的重点。

2. 单向 channel

正如上一节所描述的,有些 channel 是用于传入数据,而有些是用于传出的。单纯的依靠参数名 out 和 in 来做区分并不十分严格。为什么这么说呢?比如函数:

func counter(out chan int)

尽管 counter 函数的参数告诉我们 out 是传出参数,但是作为实现者,谁参保证他一定就不会从 out 读取数据呢?such as:

func counter(out chan int) {
var y int
for x := 0; x < 100; x++ {
out <- x
y = <- out // 你几乎无法阻止有些程序员做这样的事情,尤其是函数逻辑复杂的情况下。
out <- x
}
close(out)
}

万一真的出现这种情况,导致程序出现 bug,那就非常难查……因此,我们希望 Golang 编译器帮我做这样的限制—— 有些类型的 channel 只能读,而有些只能写。

这样一来,大多数这种『意外』的错误就能被编译器检查出来,减少犯错的机会。那如何声明这种单向 channel,非常简单:

// 下面的写法没什么意义,但是为了说明问题,只是作为一个示例
out := make(<-chan int) // 创建一个只能读的 channel
in := make(chan<- int) // 创建一个只能写的 channel

通常创建一个单向的 channel 并没什么意义,但是在 Golang ,允许将双向通道赋值给单向通道:

var reader <-chan int         // 声明只读单向通道
naturals := make(chan int) // 创建双向通道
reader = naturals // 将双向通道赋值给单向通道
naturals <- 100 // OK!
reader <- 100 // NOT OK!
x := <- reader // OK!

有同学可能记不住单向通道的箭头怎么写,到底是

  • ​->chan int​
  • ​chan-> int​
  • ​<-chan int​
  • ​chan<- int​

是不是彻底凌乱了?其实很容易记忆,所有向右的箭头都是错误的。那么就剩下 ​​<-chan int​​​ 和 ​​chan<- int​​​ 的区别了。这更简单,​​chan​​​ 是通道,那 ​​<-chan int​​​ 就表示数据从通道流出,而 ​​chan<- int​​ 则表示数据流入通道。

还有一点提示的是,在 Golang 中,​​<-​​​ 和 ​​chan​​ 关键字总是连起来写,中间不要有空格。

好了,我们继续把第 1 节的程序改写一下:

package main

import "fmt"

// 对于 counter 来说,只能往 out 里写数据
func counter(out chan<- int) {
for x := 0; x < 100; x++ {
out <- x
}
close(out)
}

// 对于 squarer 来说,只能往 out 里写数据,只能从 in 读数据
func squarer(out chan<- int, in <-chan int) {
for x := range in {
out <- x * x
}
close(out)
}

func main() {
naturals := make(chan int)
squares := make(chan int)

go counter(naturals)
go squarer(squares, naturals)

for x := range

3. 总结

  • 掌握单向通道声明
  • 知道单向通道存在的意义