go语言单向通道
在某些场景下我们可能会将通道作为参数在多个任务函数间进行传递,通常我们会选择在不同的任务函数中对通道的使用进行限制,比如限制通道在某个函数中只能执行发送或只能执行接收操作。
想象一下,我们现在有Producer
和Consumer
两个函数,其中Producer
函数会返回一个通道,并且会持续将符合条件的数据发送至该通道,并在发送完成后将该通道关闭。
而Consumer
函数的任务是从通道中接收值进行计算,这两个函数之间通过Processer
函数返回的通道进行通信。完整的示例代码如下。
package main
import (
"fmt"
)
// Producer 返回一个通道
// 并持续将符合条件的数据发送至返回的通道中
// 数据发送完成后会将返回的通道关闭
func Producer() chan int {
ch := make(chan int, 2)
// 创建一个新的goroutine执行发送数据的任务
go func() {
for i := 0; i < 10; i++ {
if i%2 == 1 {
ch <- i
}
}
close(ch) // 任务完成后关闭通道
}()
return ch
}
// Consumer 从通道中接收数据进行计算
func Consumer(ch chan int) int {
sum := 0
for v := range ch {
sum += v
}
return sum
}
func main() {
ch := Producer()
res := Consumer(ch)
fmt.Println(res) // 25
}
从上面的示例代码中可以看出正常情况下Consumer
函数中只会对通道进行接收操作,但是这不代表不可以在Consumer
函数中对通道进行发送操作。作为Producer
函数的提供者,我们在返回通道的时候可能只希望调用方拿到返回的通道后只能对其进行接收操作。但是我们没有办法阻止在Consumer
函数中对通道进行发送操作。
Go语言中提供了单向通道来处理这种需要限制通道只能进行某种操作的情况
<- chan int // 只接收通道,只能接收不能发送
chan <- int // 只发送通道,只能发送不能接收
其中,箭头<-
和关键字chan
的相对位置表明了当前通道允许的操作,这种限制将在编译阶段进行检测。另外对一个只接收通道执行close也是不允许的,因为默认通道的关闭操作应该由发送方来完成。
我们使用单向通道将上面的示例代码进行如下改造。
// Producer2 返回一个接收通道
func Producer2() <-chan int {
ch := make(chan int, 2)
// 创建一个新的goroutine执行发送数据的任务
go func() {
for i := 0; i < 10; i++ {
if i%2 == 1 {
ch <- i
}
}
close(ch) // 任务完成后关闭通道
}()
return ch
}
// Consumer2 参数为接收通道
func Consumer2(ch <-chan int) int {
sum := 0
for v := range ch {
sum += v
}
return sum
}
func main() {
ch2 := Producer2()
res2 := Consumer2(ch2)
fmt.Println(res2) // 25
}
这一次,Producer
函数返回的是一个只接收通道,这就从代码层面限制了该函数返回的通道只能进行接收操作,保证了数据安全。很多读者看到这个示例可能会觉着这样的限制是多余的,但是试想一下如果Producer
函数可以在其他地方被其他人调用,你该如何限制他人不对该通道执行发送操作呢?并且返回限制操作的单向通道也会让代码语义更清晰、更易读。
在函数传参及任何赋值操作中全向通道(正常通道)可以转换为单向通道,但是无法反向转换。
var ch3 = make(chan int, 1)
ch3 <- 10
close(ch3)
Consumer2(ch3) // 函数传参时将ch3转为单向通道
var ch4 = make(chan int, 1)
ch4 <- 10
var ch5 <-chan int // 声明一个只接收通道ch5
ch5 = ch4 // 变量赋值时将ch4转为单向通道
<-ch5
、
下面的表格中总结了对不同状态下的通道执行相应操作的结果。
**注意:**对已经关闭的通道再执行 close 也会引发 panic。