Golang 并发编程-select 语句
1. 基本介绍
select
语句能够从多个可读或者可写的 Channel 中选择一个继续执行 ,若没有Channel发生读写操作,select
会一直阻塞当前 Goroutine。
select 中 , 除了 default 之外 , 其他操作都必须要对 channel 进行操作
func SelectTest() {
//1.声明需要的变量
var array [3]int
var c1, c2, c3, c4 = make(chan int), make(chan int), make(chan int), make(chan int)
var i1, i2 int
//2.用于select的goroutine
go func() {
c1 <- 10
}()
go func() {
<-c2
}()
go func() {
close(c3)
}()
go func() {
c4 <- 40
}()
//3.多路监听的select
go func() {
select {
//监听是否可以从c1中获取值
case i1 = <-c1:
fmt.Println("接收到了C1的值:", i1)
//监听对c2的写操作
case c2 <- i2:
fmt.Println("向c2写入了:", i2)
//c3是否被关闭
case i3, ok := <-c3:
if ok {
fmt.Println("从接收到了:", i3)
} else {
fmt.Println("c3已被关闭")
}
//测试左值表达式的执行实际
case array[f()] = <-c4:
fmt.Println("从c4收到", array[f()])
//默认
default:
fmt.Println("没有channel被操作")
}
}()
//简单阻塞
time.Sleep(500 * time.Millisecond)
}
func f() int {
fmt.Println("f()执行")
return 2
}
2.: 执行流程
第一步: 收集 case 涉及的 channel
-
计算操作数 , 计算结果是一组从中接收或者发送到的 channel , 以及要发送的相应值 , 计算内容如下
-
receive 操作的 channel 操作数 case value := <- channel
-
send语句的 channel 和右表达式 case channel <- func()
-
Receive 的左侧带有短变量声明或赋值的表达式不会被计算 case func() <- channel
第二步: 判断是否存在可操作的 channel
-
如果存在 , 随机选择可执行 channel 的 case , (在第一步的时候做了一个伪随机数的随机排序)
-
如果不存在可操作的 case , 判断是否有可操作的 default , 并执行
第三步: 如果没有可操作的 case , 并且也没有可执行的 default case , 那么就会被阻塞
3. for + select
select
匹配到可操作的case
或者是defaultcase
后,就执行完毕了。实操时,我们通常需要持续监听某些channel
的操作,因此典型的select
使用会配合for
完成
func ForSelect() {
// 定义channel
ch := make(chan int)
// 向 channel 发送数据
go func() {
//实际工作中,ch可能来自缓存,网络,数据库
for {
ch <- rand.Intn(1000)
time.Sleep(200 * time.Millisecond)
}
}()
// 持续监听 channel
go func() {
for {
select {
case value := <-ch:
fmt.Println("接收到了来之channel的数据:", value)
}
}
}()
time.Sleep(3000 * time.Millisecond)
}
4. 阻塞 select
4.1 空 select 阻塞
func BlockChannel() {
// 空select阻塞
fmt.Println("before select")
select {}
fmt.Println("after select")
}
4.3 nil channel 阻塞
nil channel 不能读写
func BlockChannel() {
//nil channel
var channel chan int //nil channel 不能读写
go func() {
channel <- rand.Intn(1000)
}()
fmt.Println("before select")
select {
case <- channel:
case channel <- 100:
}
fmt.Println("after select")
}
5. nil channel 的 case
nil channel
不能读写,因此通过将 channel 设置为 nil,可以控制某个case
不再被执行。
- 例如,3秒后,不再接受ch的数据:
func NilChannel() {
// 一:初始化channel
channel := make(chan int)
// 二:操作 channel 的 goroutine
go func() {
//向 channel 中写入随机数
for {
channel <- rand.Intn(1000)
time.Sleep(400 * time.Millisecond)
}
}()
// 三:select 处理内部的 goroutine
go func() {
// 设置定时器
after := time.After(3 * time.Second)
sum := 0
// 持续监听 channel
for {
select {
case value := <-channel:
fmt.Println("接收到channel的数据:", value)
sum++
// 定时器时间到了之后,就可以从里面读出内容
case <-after:
// 将channel设置为nil channel
channel = nil
fmt.Println("channel已设置为nil,sum为:", sum)
}
}
}()
// 让主 goroutine 阻塞
time.Sleep(5 * time.Second)
}
6. 带有 default 的 select
当 select 语句存在 default case 时:
-
若没有可操作的 channel,会执行 default case
-
若有可操作的 channel,会执行对应的 case
这样 select 语句不会进入 block 状态,称之为非阻塞 ( non-block ) 的收发 ( channel ) 的接收和发送
示例:多个 goroutine 猜数字游戏,是否有 goroutine 猜中数字:
func SelectNonBlock() {
// 初始化数据
counter := 10 // 参与人数
max := 20 // [0, 19] // 最大范围
rand.Seed(time.Now().UnixMilli())
answer := rand.Intn(max) // 随机答案
println("The answer is ", answer)
println("------------------------------")
// 正确答案channel
bingoCh := make(chan int, counter)
// wg
wg := sync.WaitGroup{}
wg.Add(counter)
for i := 0; i < counter; i++ {
// 每个goroutine代表一个猜数字的人
go func() {
defer wg.Done()
result := rand.Intn(max)
println("someone guess ", result)
// 答案争取,写入channel
if result == answer {
bingoCh <- result
}
}()
}
wg.Wait()
println("------------------------------")
// 是否有人发送了正确结果
// 可以是0或多个人
// 核心问题是是否有人猜中,而不是几个人
select {
case result := <-bingoCh:
println("some one hint the answer ", result)
default:
println("no one hint the answer")
}
}
特别的情况是存在两个 case,其中一个是 default ,另一个是 channel case,那么 go 的优化器会优化内部这个 select 。内部会以 if 结构完成处理。因为这种情况,不用考虑随机性的问题
select {
case result := <-bingoCh:
println("some one hint the answer ", result)
default:
// 非阻塞的保证,存在default case
println("no one hint the answer")
}
// 优化伪代码
if selectnbrecv(bingoCh) {
println("some one hint the answer ", result)
} else {
println("no one hint the answer")
}