066-单向 channel

Posted --Allen--

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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. 总结

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

以上是关于066-单向 channel的主要内容,如果未能解决你的问题,请参考以下文章

Java NIO系列 - Channel

go语言之并发编程 channel

来自不同 AD7606 通道的混合电压读数

Golang 基础:原生并发 goroutine channel 和 select 常见使用场景

Golang 基础:原生并发 goroutine channel 和 select 常见使用场景

go语言学习笔记 — 进阶 — 并发编程:通道(channel) —— 各种各样的通道