您的位置 首页 golang

进大厂系列-Golang基础-select篇-04

1. select是用来干什么的?

select是用来监听多个channel的读写事件的。其用法与switch有些类似,只不过其每个case都是channel的读写操作。

2.select的源码目录在哪里?

runtime.select 目录下面。

3.select每一个case的存储结构是怎么样子的?

在select文件中,有个scase的结构体用来保存case的信息,其包含,一个hchan用来存储对应的channel信息,以及一个elem,用来指向接收或者发送信息的存储地址。

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

4.select的分类?

select分为阻塞和非阻塞两类,其中不带default的是阻塞的,而带default是非阻塞的

  • 阻塞
 select {
case <- ch:
   fmt.Println("baba")
}  
  • 非阻塞
 select {
case <- ch:
   fmt.Println("baba")
default:
}  

5.selectgo函数有几个参数,每个参数的意义是啥?

selectgo函数是select的主要逻辑函数,其每个参数的意义如下:

  • case0: case0指向case数组,ncase代表的是case的个数,ncase必须小于2^16次方
  • order0: 指向一个一个2 * ncase大小的数组
  • pc0: 只想一个ncases大小的数组,用来做数据竞争的时候会使用到
  • nsends: 类型是发送的case个数
  • nrecvs: 类型是接受的case个数
  • block: 是否阻塞,已知select加了default之后里面的channel可以不阻塞

6.为什么ncases的数量不超过2^16呢?

因为保存cases的数据结构都是存储在goroutine的堆栈里面,为了保持一个精简的堆栈,其设置了上限为65536个。猜测再变大的话goroutine的切换开销可能就会变大,因为需要保存goroutine的堆栈信息。

9.selectgo函数scase,pollorder,lockeroder分别是用来干嘛的?

  • scases,用来存储每个case的
  • pollorder,用来存储便利case的顺序
  • lockorder,按照hchan的地址进行排序,然后按照顺序来锁定所有的channel,对channel的操作是上锁的,否则同时操作 channel就会出并发问题。

10.这段代码是如何将pollorder打乱的?

 norder := 0
for i := range scases {
   cas := &scases[i]

   // Omit cases without channels from the poll and lock orders.
   if cas.c == nil {
      cas.elem = nil // allow GC
      continue
   }

   j := fastrandn(uint32(norder + 1))
   pollorder[norder] = pollorder[j]
   pollorder[j] = uint16(i)
   norder++
}  

对于阅读代码主要是两种方式,一种是举例,一种是画图。这个代码的关键在于fastrandn随机生成数的范围,其生成的范围为[0,norder+1),相当于让pollorder[norder] = pollorder[j],然后让pollorder[j] = uint16(i)当前最新的索引。模拟一下就很好理解他在做什么,类似于交换。

11.pollorder是如何排序的?

pollorder是根据hchan的地址进行堆排序的,nlogn的时间复杂度,和常量的内存

12.为什么需要锁住所有的channel?

因为对channel的操作是非并发安全的,比如从sendq或者recvq里面取数据,或者从缓冲里面取数据。

13. 寻找准备好的channel是怎么样的逻辑?

遍历pollorder,比较pollorder所记录的索引与nsends的大小。

接收逻辑:

如果casi >= nsends那么这个case就是来接收消息的,然后首先看sendq是否有挂起的sudog如果有,则获取suog所携带的消息,然后给所有channel解锁,返回。如果没有挂机的sudog,则查看是否有缓冲区以及缓冲区的元素是否大于0,其实只需要看qcount这个参数是否大于0则可,如果qcount>0,则从缓冲区中取数据,解锁所有的channel,返回。如果qcount也不大于0,则判断channel是否已关闭,如果已关闭则解锁所有的channel, 并且把接收数据的标志改成false。

发送逻辑:

如果casi < nsends那么这个case就是来发送消息的,首先判断channel是不是已经关闭了,如果channel已经关闭了,则解开所有的channel,然后pannic。然后再看recvq是否有挂起的sudog,如果有的话,则从recvq中取出一个sudog把数据给他,并将其唤醒,然后返回,否则看是否有缓冲区,以及缓冲区是否已经满了,如果没满则直接放到缓冲区,然后返回。

如果执行接受逻辑,sendq既没有sudog,缓冲区中也没有元素,并且channel也没有关闭,如果执行发送逻辑,recvq既没有sudog,缓冲区中也没有元素,channel也没有关闭。则继续判断是否是阻塞调用?如果非阻塞,则释放channel的锁,然后返回,否则阻塞当前goroutine,然后进行调度

14.select阻塞当前goroutine的逻辑是怎么样的?

其首先获取当前正在允许的goroutine,然后遍历locker,将当前goroutine加入到每一个channel的等待队列当中,修改goroutine状态为parkingOnChan = 1,然后调用gopark()进行goroutine调度,等待唤醒。调用gopark传入的selparkcommit应该有给当前g所对应的所有goroutine解锁。

15.阻塞在select的channel唤醒之后做了什么呢?

首先按lockeroder锁住channel,然后获取被唤醒的sudog,然后遍历lockorder,找出唤醒的那个channel所对应的case,然后吧sudog从其他channel的等待队列删除,然后返回。

文章来源:智云一二三科技

文章标题:进大厂系列-Golang基础-select篇-04

文章地址:https://www.zhihuclub.com/87144.shtml

关于作者: 智云科技

热门文章

网站地图