非零基础自学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