转载自:https://blog.csdn.net/u012279631/article/details/80691161

单向channel

近日在面试中有提到过单向channel。问我是否了解。之前在golang的官方库中确实有看到相应的单向channel的例子,如context包,以及在使用第三方包imap的时候也有使用单向channel,那个是作为通知使用。这里再总结一下单向channel的语法以及为什么使用单向channel的一些看法。

语法

chan<- //指向channel 表示 单向只写
<-chan // 指出channel 表示 单向只读


例子

在 《go 语言圣经》中有一个例子。讲述从一个简单的例子一步步到使用单向channel。

单向channel_单向channel

三个协程

  • 第一个goroutine是一个计数器,用于生成0、1、2、……形式的整数序列,然后通过channel将该整数序列发送给第二个goroutine;

  • 第二个goroutine是一个求平方的程序,对收到的每个整数求平方,然后将平方后的结果通过第二个channel发送给第三个goroutine;

       第三个goroutine是一个打印程序,打印收到的每个整数。为了保持例子清晰,我们有意选择了非常简单的函数,当然三个goroutine的计算很简单,在现实中确实没有必要为如此简单的运算构建三个goroutine。
        

func main() {
   naturals := make(chan int)
   squares := make(chan int)
   // Counter
   go func() {
      for x := 0; ; x++ {
         naturals <- x
      }
   }()
   // Squarer
   go func() {
      for {
         x := <-naturals
         squares <- x * x
      }
   }()
   // Printer (in main goroutine)
   for {
      fmt.Println(<-squares)
   }
}

如果我们希望通过Channels只发送有限的数列该如何处理呢? 
如果发送者知道,没有更多的值需要发送到channel的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,因为接收者可以停止不必要的接收等待。这可以通过内置的close函数来关闭channel实现:

close(naturals)

一个channel被关闭后,再向该channel发送数据将导致panic异常。当一个被关闭的channel中已经发送的数据都被成功接收后,后续的接收操作将不再阻塞它们会立即返回一个零值。关闭上面例子中的naturals变量对应的channel并不能终止循环,它依然会收到一个永无休止的零值序列,然后将它们发送给打印者goroutine。

解决方式:

  • 使用 ok来判断channel是否关闭。

// Squarer
go func() {
   for {
      x, ok := <-naturals
      if !ok {
         break // channel was closed and drained
      }
      squares <- x * x
   }
   close(squares)
}()

  • 使用range 当channel关闭的时候,会自动跳出来。

  • 最终写法如下:

  • func counter(out chan<- int) {
       for x := 0; x < 5; x++ {
          out <- x
       }
       close(out)
    }
    func squarer(out chan<- int, in <-chan int) {
       for v := range in {
          out <- v * v
       }
       close(out)
    }
    func printer(in <-chan int) {
       for v := range in {
          fmt.Println(v)
       }
    }
    func main() {
       naturals := make(chan int)
       squares := make(chan int)
       go counter(naturals)
       go squarer(squares, naturals)
       printer(squares)
    }

  • 输出:

  • 0

  • 1

  • 4

  • 9

  • 16


  • Process finished with exit code 0

  • 这里使用了单向channel。很明显数据的流向是单向的。获取的地方不应该对channel赋值。这样把一个双向的channel转为一个单向的channel能够防止channel被滥用。降低了风险。

    注意

        任何双向channel向单向channel变量的赋值操作都将导致该隐式转换。这里并没有反向转换的语法:也就是不能一个将类似 chan<- int 类型的单向型的channel转换为 chan int 类型的双向型的channel


总结

  1. 1.作为函数函数的时候,channel总是取值或者赋值的单一功能。这时候把声明做成单向channel能够避免滥用的情形。

  2. 2.通知相应协程动作处理完毕也可以使用channel方式。

  3. 3.channel 关闭后不能再写,会panic。用 _,ok := channel 方式或者是range可以判断或者避免。

  4. 4.双向channel转为单向后不能够再次转为双向channel。