Select实现优先级并发技巧
序言
Go语言中使用 Select 语句可以同时对多个case进行监听,它通常与多个 channel 一起使用去进行协程的资源监听。当 Select 的其中一个 case 满足条件时,Select 就会执行对应的 case 分支。
这里我们写一个简单的程序去运行看看他的特性:
func main() {
numList := make(chan int, 5)
strList := make(chan string, 1)
go func() {
for i := 0; i < 5; i++ {
numList <- i
}
strList <- fmt.Sprintf("曼波曼波")
}()
for {
select {
case num := <-numList:
fmt.Println(num)
case str := <-strList:
fmt.Println(str)
return
}
}
}运行结果:
# 第一次运行结果:
0
1
曼波
# 第二次运行结果:
0
曼波
# 第三次运行结果:
0
1
2
曼波
# 第四次运行结果:
0
1
2
3
4
曼波我们会发现上面的运行结果基本每次都不一样,这是因为 Select 的特性导致的,Select 和 Switch 不同,它并不按照代码书写的顺序去进行判断分支条件是否满足,而是会(伪)随机的选择一个满足条件的 case 分支进行执行。
因此上面的代码并不是先打印完 numList 再打印 strList。上面代码开启了两个协程,因为子协程将两个 channel 需要的值都赋予了给它们,但是当主协程去进行 Select 读取的时候,因为两个 case 都满足条件,会触发 Select 的(伪)随机特性,使得每次运行的结果都可能不一样。
因此,想要在多个满足条件的 case 上先运行我们想要的 case,我们就需要给这部分加个优先级。
优先级
例如我们期望先打印完 numList 再打印 strList 后结束,我们就可以这样写:
func main() {
numList := make(chan int, 5)
strList := make(chan string, 1)
go func() {
for i := 0; i < 5; i++ {
numList <- i
}
strList <- fmt.Sprintf("曼波曼波")
}()
for {
select {
case num := <-numList:
fmt.Println(num)
case str := <-strList:
for {
select {
case num := <-numList:
fmt.Println(num)
default:
fmt.Println(str)
return
}
}
}
}
}输出结果:
# 输出结果:
0
1
2
3
4
曼波曼波这样,使用双层 for 循环嵌套去判断就行了,里层 for 循环的 Select 选择,当 num 的 case 满足条件时,就运行它,不然就运行 str 的默认 case 。因为之前一个 Select 里面两个 case 会存在随机,当里层用了 default 之后,有其他的 case ,就会优先选择其他 case 而不是 default 的内容啦。
额外
具体K8s中也有利用 Select 优先级去处理 node 与 pod 的优先级更新,优先更新 node 再更新 pod。
for {
select {
case <-stopCh:
return
case nodeUpdate := tc.nodeUpdateChannels[workers]:
tc.handleNodeUpdate(ctx, nodeUpdate)
tc.nodeUpdateQueue.Done(nodeUpdate)
case podUpdate := <- tc.podUpdateChannels[works]:
//If we found a Pod update we need to empty Node queue first.
pirority:
for {
select {
case nodeUpdate := tc.nodeUpdateChannels[workers]:
tc.handleNodeUpdate(ctx, nodeUpdate)
tc.nodeUpdateQueue.Done(nodeUpdate)
default:
// 跳出pirority外,跳出到for循环
// 如果不这样做,只能break出下一次的Select
break pirority
}
}
// After Node queue is emptied we process podUpdate.
tc.handlePodUpdate(ctx, podUpdate)
tc.PodUpdateQueue.Done(podUpdate)
}
}这里因为要一直轮询优先去查 node 是否更新,高优先级的 node 正常更新,当没有高优先级的 node 准备就绪了,K8s就需要保证清空完 node 的升级,再去更新 pod。(意思就是始终优先更新node)
所以里侧 for 循环如果没有 node 就绪更新,要跳出再去更新对应的 pod ,之后触发下一次外层 for 循环一直轮询优先查找 node 是否更新。
总结
至此,这就是Go语言中 Select 的一个优先级的并发技巧。我也只是听别人的讲解学的,感觉有用就记录了下来。
RoLingG | 博客
评论(0)