select语句介绍

select 是 Go 中的一个控制结构。select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。但是 select 语句中所有 case 中的表达式都必须是 channel 的发送或接收操作。

select 语句的语法结构和switch语句很相似,也有case语句和default语句:

select {    case communication clause  :       statement(s);          case communication clause  :       statement(s);     /* 你可以定义任意数量的 case */    default : /* 可选 */       statement(s);}

说明:

  • 每个case都必须是一个通信

  • 所有channel表达式都会被求值

  • 所有被发送的表达式都会被求值

  • 如果有多个case都可以运行,select会随机公平地选出一个执行。其他不会执行。

  • 否则:

    如果有default子句,则执行该语句。

    如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。

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

空select

内部不包含任何case语句的,即为空的select,例如:

select{}

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

只有一个case

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

select {case   fmt.Println("liwenzhou.com")}

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

有default语句

当其他case都没有满足时,select中还可以包含default语句,用于执行一些默认操作,可以避免阻塞。

select {case   fmt.Println("liwenzhou.com")default:  time.Sleep(time.Second)}

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

如何在select中实现优先级

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

现在我们有一个需求:我们有一个函数会持续不间断地从ch1和ch2中分别接收任务1和任务2,

如何确保当ch1和ch2同时达到就绪状态时,优先执行任务1,在没有任务1的时候再去执行任务2呢?

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

func worker(ch1, ch2 chan int, stopCh chan struct{}) {  for {    select {    case       return    case job1 :=       fmt.Println(job1)    default:      select {      case job2 :=         fmt.Println(job2)      default:      }    }  }}

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

怎么办呢?

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

func worker2(ch1, ch2 chan int, stopCh chan struct{}) {  for {    select {    case       return    case job1 :=       fmt.Println(job1)    case job2 :=     priority:      for {        select {        case job1 :=           fmt.Println(job1)        default:          break priority        }      }      fmt.Println(job2)    }  }}

这一次,小明不仅使用了嵌套的select,还组合使用了for循环和LABEL来实现题目的要求。

上面的代码在外层select选中执行 job2 :=

实际应用场景

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

// 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       return    case nodeUpdate :=       tc.handleNodeUpdate(nodeUpdate)      tc.nodeUpdateQueue.Done(nodeUpdate)    case podUpdate :=       // 如果我们发现了一个 Pod 需要更新,我么你需要先清空 Node 队列.    priority:      for {        select {        case nodeUpdate :=           tc.handleNodeUpdate(nodeUpdate)          tc.nodeUpdateQueue.Done(nodeUpdate)        default:          break priority        }      }      // 在 Node 队列清空后我们再处理 podUpdate.      tc.handlePodUpdate(podUpdate)      tc.podUpdateQueue.Done(podUpdate)    }  }}

总结

本文回顾了Go语言中select语句的一些用法,并延伸出了一个如何在select中实现优先级的小技巧,希望能对大家有所帮助。

最后,Go语言由于自身没有很多奇怪的语法糖和自带代码格式化,相比其他语言来说并不会存在看不懂别人写的代码的情况。所以我们完全可以通过阅读优秀库的源代码,站在巨人的肩膀上,看的更远。

完整示例代码归档地址:

https://github.com/Scoefield/gokeyboardman/tree/main/goselect

以上是关于Go语言select语句优先级实现的介绍,感谢您的阅读,如有疑问或意见请及时反馈给我们。


上一篇文章:

Golang操作数据库Redis