上一节我们已经学习的通道的基本用法,也练习了缓冲通道和非缓冲通道。我们本届继续学习通道的其它功能特性。
单向通道
所谓单向通道,也就是只能向通道中发送数据,或只能从通道中读取数据。
按通道方向分
单向通道
<-chan int 接收通道,从通道内读取通道
chan <- int 发送通道,向通道内发送内容
一般用在函数参数上,对函数内的通道行为做约束
双向通道
var c = make(chan int) 正常声明的通道
go会自动把双向通道转为单向通道
定义一个发送通道:
receiveChan := make(chan<-int,0)
单向通道的作用是什么
我们前面讲过,通道是用于传输数据的,那么通道如果只能发送或接收数据,那么就是这个通道只有一头能工作,比如作为一个单向的发送通道,不能从里面读取数据,又有什么用呢?
简单来说,单向通道是用来对代码做出限制,比如某些地方只希望从通道中读取数据,而不希望在这里也向通道写入数据。
package main
import "fmt"
func main() {
ch := make(chan int,1)
sentChan(ch)
fmt.Println(<-ch)
}
func sentChan(ch chan <-int) {
ch <- 100
}
输出:100
从sentChan函数我们可以看出来,在函数里ch因为限制了是只能发送单向通道,所以在函数内不能从通道读取数据,如果写了 <-ch,那么编译器会报错:
Invalid operation: <- ch (receive from send-only type chan<- int) less... (Ctrl+F1)
Inspection info: Reports incompatible types in binary and unary expressions.
但是在调用sentChan函数的代码中,ch却是一个正常的通道,既能读也能写。
这里是不是很巧妙,编程之美也就在于此,很优雅的就对代码做了安全的限制。
如果是将单向通道作为返回值返回,那么就是对调用方做了限制,拿到的返回通道,要么只能读,要么只能写。
select在通道上的使用
select是专门和通道配合使用的,效果和switch类似,选择符合的case执行。
语法格式:
select {
case 表达式1:代码
case 表达式2:代码
default: 代码
}
如果没有case符合执行条件,那么会执行default分支
package main
import (
"fmt"
"math/rand"
)
func main() {
chs := [3]chan int{
make(chan int,1),
make(chan int,1),
make(chan int,1),
}
//返回一个取值范围在[0,3)的伪随机int值
intn := rand.Intn(3)
fmt.Println("ints :", intn)
chs[intn] <- intn
select {
case <- chs[0]:
fmt.Println("chan 0")
case <- chs[1]:
fmt.Println("chan 1")
case <- chs[2]:
fmt.Println("chan 2")
default: fmt.Println("没有符合执行条件的代码")
}
}
还有一些使用时要注意的点:
1、如果所有case都没有符合执行条件的,那么就执行default;
2、如果没有default,那么当前select就会阻塞,直到有通道满足表达式,则停止阻塞恢复执行;
3、如果select中有多个case满足执行条件,那么会随机选择一个执行;
4、如果从一个已经关闭的通道中读取数据,那么将引发panic,所以在需要的时候,要通过读取通道的第二个参数来判断通道是否可用;
类似这种写法:
case _, ok := <-ch:
if !ok {
fmt.Println("通道已关闭.")
break
}
5、每次执行select,select的case都会从上到下执行一遍所有表达式。
通道引发的panic
1、对一个已经关闭的通道,进行通道发送操作,会引发panic;
2、对已经关闭的通道再次关闭,会引发panic;