3

Go 语言中 select 语句的选择顺序

 2 years ago
source link: https://liqiang.io/post/ordering-of-select-statements-in-go?lang=ZH_CN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在 Go 里面,有个很常用的 select 语句,用平时都会用,但是你知道它底层的实现机制吗?我不知道,所以我就学习了一下,顺便记录一下结论。

channel 的工作机制

在开始说 select 之前,先来收一下 unbuffer 的 channel 是怎么工作的,其实 buffer channel 在 buffer 满的时候也是类似的机制,那就先说 unbuffer 吧。

在 channel 的内部,有两个数据结构,分别是 sendqrecvq,他们用于放置因为发送消息到 channel 或者从 channel 中读取消息而阻塞的 goroutine;当然,对于发送消息而阻塞的 goroutine 除了存放 goroutine 指针之外,还会存放发送的消息指针。

如果一个有阻塞的 recv goroutine 遇到一个发送来的消息的时候,channel 就会从 recvq 中出队一个接收 goroutine,然后通过 memmove 将消息 copy 给他,从而完成一个 channel 的消息交换,内部数据结构类似于:

68f0f97ecbe8

对于 buffer channel,其实是多了一个 buffer 的存储,在 Go 里面,buffer 的存储是类似于一个环状队列实现,在 channel 结构中有一下属性:

8f012825a009

  • buffer:一个固定长度的数组
  • qcount:当前 buffer 中有多少个元素
  • dataqsiz:buffer 的长度
  • sendx:下一个存储元素的位置
  • recvx:下一个返回元素的位置

通过这几个变量,就可以实现环形数组了。

select 的实现

当了解完 channel 的实现之后,再来理解 select 就比较容易一些了,例如这一段代码:

  1. [[email protected]]# cat main.go
  2. select {
  3. case <- a:
  4. case <- b:
  5. case <- c:
  6. }

当 a、b 和 c channel 都阻塞的时候,在 Go 的实现里面,其实就是在 a、b 和 c 三个 channel 的等待队列里面都将 select 这个 goroutine 注册上,当这三个中只要有一个返回了之后,select 对应的 goroutine 就会得到激活并运行,只不过 Go 需要额外地完成其他未激活 channel 的出队列操作。

d4349700c683

但是,Go 在某些条件下,会简化问题,例如下面这段带 default 的代码:

  1. [[email protected]]# cat default.go
  2. select {
  3. case <- a:
  4. default:
  5. }

如果 channel a 是阻塞的,其实 select 就不用往 channel 的等待队列中插入 goroutine 了,因为这个时候直接执行 default 就可以了,所以 Go 的简化逻辑就是:对于这种带 default 的 select,直接检查对应的 channel 是否有数据,如果没有直接执行 default,有数据,直接获取数据,并运行对应的代码。

还有一种更简单的逻辑,Go 的简化也不一样:

  1. [[email protected]]# cat single.go
  2. select {
  3. case <- a:
  4. }

对于这种只有一个 channel 的,其实就是我们平时使用 channel 的简单方式,所以可以直接简化成:

  1. [[email protected]]# cat single2.go
  2. <- a

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK