066-单向 channel

在 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 squares {
        fmt.Println(x)
    }
}

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 intchan<- 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 squares {
        fmt.Println(x)
    }
}

3. 总结

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

猜你喜欢

转载自blog.csdn.net/q1007729991/article/details/80794938