非零基础自学Golang

第13章 并发与通道

13.3 channel

13.3.3 close 和range

当发送者知道没有更多的值需要发送到channel时,让接收者也能及时知道没有更多的值需要接收是很有必要的,因为这样就可以让接收者停止不必要的等待。

这可以通过内置的close函数和range关键字来实现。

【1】close

使用close关闭channel时需要注意:

  • channel不像文件一样需要经常去关闭,只有当你确实没有任何需要发送的数据时,或者想要显式地结束range循环之类的,才会去关闭channel。
  • 关闭channel后,无法向channel再次发送数据,再次发送将会引发panic错误。
  • 关闭channel后,可以继续从channel接收数据。
  • 对于nil channel,无论接收还是发送都会被阻塞。

[ 动手写13.3.4]

package main

import "fmt"

func main() {

   ch := make(chan int, 3)

   go func() {
      for i := 0; i < 3; i++ {
         fmt.Printf("len(ch) = %v , cap(ch) = %v\n", len(ch), cap(ch))
         ch <- i
      }
      close(ch)
   }()

   for {
      if val, ok := <-ch; ok == true {
         fmt.Println(val)
      } else {
         return
      }
   }
}

动手写13.3.4中,发送者在发送完数据后,使用了close关闭channel。channel被关闭后,ok的值就会变为false,最终程序结束运行。运行结果如下:

【2】range

除了使用上面的方式来遍历channel,还可以使用更加简洁的range关键字,格式如下:

for data := range ch {

}

[ 动手写 13.3.5]

package main

import "fmt"

func main() {

   ch := make(chan int, 3)
   go func() {
      for i := 0; i < 3; i++ {
         fmt.Printf("len(ch) = %v , cap(ch) = %v\n", len(ch), cap(ch))
         ch <- i
      }
      close(ch)
   }()

   for data := range ch {
      fmt.Println(data)
   }
}

动手写13.3.5使用range遍历了channel,当channel关闭后,range也能自动结束本次遍历。

运行结果如下:

13.3.4 单向channel

默认情况下,通道是双向的,就是既可以向通道发送数据,也可以从通道中接收数据。但是,我们建一个通道作为参数进行传递,经常希望只单向使用,即要么只发送数据,要么只接收数据,这时候就可以指定通道的方向,也就是使用单向通道。

单向channel变量的声明非常简单,如下:

var ch1 chan int // ch1为一个双向通道

var ch2 chan<- int // ch2为一个只能接收的单向通道

var <-chan int // ch3为一个只能发送的单向通道

“chan<-”表示数据进入通道,只要把数据写入通道,对于调用者而言就是输出。“<-chan”表示数据从通道中出来,对于调用者就是得到通道的数据,也就是输入。

[ 动手写 13.3.6]

package main

import "fmt"

func producer(out chan<- int) {

   for i := 0; i < 10; i++ {
      out <- i
   }

   close(out)
}

func consumer(in <-chan int) {

   for val := range in {
      fmt.Println(val)
   }

}

func main() {

   ch := make(chan int)
   go producer(ch)
   consumer(ch)
}

动手写13.3.6展示了生产者—消费者模型的实现,producer是数据生产者,consumer是数据消费者,数据流向是固定的,也就是从生产者流到消费者,这种情况就适合使用单向通道。

运行结果如下:

【提示】

可以将普通的双向channel隐式转换为单向channel,不能将单向channel转换为双向channel。

13.3.5 定时器

在Go语言标准库的time包中,定时器的实现使用的就是单向channel。

我们来看看如何使用定时器实现每隔一段时间打印一串字符:

[ 动手写 13.3.7]

package main

import (
   "fmt"
   "time"
)

func main() {

   ticker := time.NewTicker(time.Second)

   for {
      <-ticker.C
      fmt.Println("loop")
   }
}

运行结果

没问题,每隔1 s打印一个 loop