Go语言在select语句中实现优先级

Go 语言中的 select语句用于监控并选择一组case语句执行相应的代码。它看起来类似于switch语句,但是select语句中所有case中的表达式都必须是channel的发送或接收操作。一个典型的select使用示例如下

csharp 复制代码
select {
case <-ch1:
	fmt.Println("liwenzhou.com")
case ch2 <- 1:
	fmt.Println("q1mi")
}

Go 语言中的 select 关键字也能够让当前 goroutine 同时等待ch1 的可读和ch2的可写,在ch1和ch2状态改变之前,select 会一直阻塞下去,直到其中的一个 channel 转为就绪状态时执行对应case分支的代码。如果多个channel同时就绪的话则随机选择一个case执行。

除了上面展示的典型示例外,接下来我们逐一介绍一些select的特殊示例。

空select

空select指的是内部不包含任何case,例如:

csharp 复制代码
select{
  
}

空的 select 语句会直接阻塞当前的goroutine,使得该goroutine进入无法被唤醒的永久休眠状态。

只有一个case

如果select中只包含一个case,那么该select就变成了一个阻塞的channel读/写操作。

csharp 复制代码
select {
case <-ch1:
	fmt.Println("liwenzhou.com")
}

上面的代码,当ch1可读时会执行打印操作,否则就会阻塞。

有default语句

如果select中还可以包含default语句,用于当其他case都不满足时执行一些默认操作。

csharp 复制代码
select {
case <-ch1:
	fmt.Println("liwenzhou.com")
default:
	time.Sleep(time.Second)
}

上面的代码,当ch1可读时会执行打印操作,否则就执行default语句中的代码,这里就相当于做了一个非阻塞的channel读取操作。

总结

  1. select 不存在任何的 case:永久阻塞当前 goroutine
  2. select 只存在一个 case:阻塞的发送/接收
  3. select 存在多个 case:随机选择一个满足条件的case执行
  4. select 存在 default,其他case都不满足时:执行default语句中的代码

如何在select中实现优先级

已知,当select 存在多个 case时会随机选择一个满足条件的case执行。

现在我们有一个需求:我们有一个函数会持续不间断地从ch1和ch2中分别接收任务1和任务2, 如何确保当ch1和ch2同时达到就绪状态时,优先执行任务1,在没有任务1的时候再去执行任务2呢?

高级Go语言程序员小明挠了挠头写出了如下函数:

go 复制代码
func worker(ch1, ch2 <-chan int, stopCh chan struct{}) {

	for {
		select {
		case <-stopCh:
			return
		case job1 := <-ch1:
			fmt.Println(job1)
		default:
			select {
			case job2 := <-ch2:
				fmt.Println(job2)
			default:
			}
		}
	}
}

上面的代码通过嵌套两个select实现了"优先级",看起来是满足题目要求的。但是这代码有点问题,如果ch1和ch2都没有达到就绪状态的话,整个程序不会阻塞而是进入了死循环。

怎么办呢?

小明又挠了挠头,又写下了另一个解决方案:

go 复制代码
func worker2(ch1, ch2 <-chan int, stopCh chan struct{}) {
	for {
		select {
		case <-stopCh:
			return
		case job1 := <-ch1:
			fmt.Println(job1)
		case job2 := <-ch2:
		priority:
			for {
				select {
				case job1 := <-ch1:
					fmt.Println(job1)
				default:
					break priority
				}
			}
			fmt.Println(job2)
		}
	}
}

这一次,小明不仅使用了嵌套的select,还组合使用了for循环和LABEL来实现题目的要求。上面的代码在外层select选中执行job2 := <-ch2时,进入到内层select循环继续尝试执行job1 := <-ch1,当ch1就绪时就会一直执行,否则跳出内层select。

实际应用场景

上面的需求虽然是我编的,但是关于在select中实现优先级在实际生产中是有实际应用场景的,例如K8s的controller中就有关于上面这个技巧的实际使用示例,这里在关于select中实现优先级相关代码的关键处都已添加了注释,具体逻辑这里就不展开细说了。

go 复制代码
// kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager.go 
func (tc *NoExecuteTaintManager) worker(worker int, done func(), stopCh <-chan struct{}) {
	defer done()

	// 当处理具体事件的时候,我们会希望 Node 的更新操作优先于 Pod 的更新
	// 因为 NodeUpdates 与 NoExecuteTaintManager无关应该尽快处理
	// -- 我们不希望用户(或系统)等到PodUpdate队列被耗尽后,才开始从受污染的Node中清除pod。
	for {
		select {
		case <-stopCh:
			return
		case nodeUpdate := <-tc.nodeUpdateChannels[worker]:
			tc.handleNodeUpdate(nodeUpdate)
			tc.nodeUpdateQueue.Done(nodeUpdate)
		case podUpdate := <-tc.podUpdateChannels[worker]:
			// 如果我们发现了一个 Pod 需要更新,我么你需要先清空 Node 队列.
		priority:
			for {
				select {
				case nodeUpdate := <-tc.nodeUpdateChannels[worker]:
					tc.handleNodeUpdate(nodeUpdate)
					tc.nodeUpdateQueue.Done(nodeUpdate)
				default:
					break priority
				}
			}
			// 在 Node 队列清空后我们再处理 podUpdate.
			tc.handlePodUpdate(podUpdate)
			tc.podUpdateQueue.Done(podUpdate)
		}
	}
}
相关推荐
陈随易13 小时前
纯 Go 写的 JS 引擎,跑分超过了 C 语言的 QuickJS
前端·后端·程序员
卷无止境14 小时前
RAGFlow 项目全景报告:从文档解析引擎到 Agent 原生数据库
后端·python
小Z先生14 小时前
我用 Go + 系统 WebView 写了个纯本地 Postman:不装 Electron、不连云端
后端·github
IT_陈寒15 小时前
Redis连接池泄漏那次,我差点把生产环境搞崩了
前端·人工智能·后端
灿飞屋15 小时前
Kotlin与Java虚拟机三:控制流
android·java·开发语言·后端·python·kotlin·安卓
石小石Orz15 小时前
AI 正在蚕食我们的编码能力
前端·后端·算法
陈随易15 小时前
专注飞机 FocusFlight,两个人把番茄钟做成了一趟航班
前端·后端·程序员
量化分析码农16 小时前
【Python量化波动率实战 04】低波动异象:低波组真的跑赢高波组吗
后端
风上猫16 小时前
MySQL多值索引:让 JSON 数组也能走索引(以及 6 个容易踩的坑)
后端