go语言——select初窥

427 阅读2分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」。

go语言学习的过程中经常会遇到select,感觉并不是很懂,导致学习过程中有些寸步难行。因此在本篇文章中专门学习下。

先举个例子

func findMinStep(board string, hand string) int {
    chan1 := make(chan int)
    chan2 := make(chan int)

    go func(){
        chan1 <- 1
        time.Sleep(time.Second)
    }
    go func(){
        chan2 <- 2
        time.Sleep(time.Second)
    }

    select {
        case <- chan1:
            fmt.Println("case1 !")
        case <- chan2:
            fmt.Println("case2 !")
        default:
            fmt.Println("default !")
    }
}

elect语句分别检测chan1和chan2是否可读,如果都不可读则执行default语句。

那么如果没有default呢

select如果没有default,就会去查看case里通道是否有值,如果通道没有值,就会造成阻塞。直到case里的通道有值为止。

那么为什么default如此特殊呢?或者说在select的时候程序究竟在做什么呢?

case语句

我们查看runtime包下面的select.go函数,发现了scase的结构体

type scase struct {
   c    *hchan         // chan
   elem unsafe.Pointer // data element
}

看起来c就代表是一个通道,也是对应了我们每个case都会有一个通道的特点。这个指针没发现是干啥用的,暂时留下。

接着往下看

简单介绍下两个加锁解锁的函数

func sellock(scases []scase, lockorder []uint16) {
   var c *hchan
   for _, o := range lockorder {
      c0 := scases[o].c
      if c0 != c {
         c = c0
         lock(&c.lock)
      }
   }
}

这个加锁代码简单,也很有意思。从参数上看是简单的scase数组和lockorder数组,通过for循环我们也可以理解为代码需要根据lockorder的值对scases数组中的部分元素进行加锁,但是

if c0 != c {
     c = c0
     lock(&c.lock)
  }

这份代码就比较奇怪,因为这代表着如果上次遍历的元素和这次加锁的对象相同,我们就不会重复加锁。

如果不想重复加锁,我们其实可以有很多种方式,而且一般来说这种也只是能在满足“如果上次遍历的元素和这次加锁的对象相同”条件下时,才不会重复加锁,看起来有点鸡肋,不过在假设代码没问题(事实上也是没问题)的情况下,我们可以大胆反推:lockorder是一个有序的数组。