首页 > 其他分享 >(转)golang学习之--select--case 原理

(转)golang学习之--select--case 原理

时间:2023-02-06 16:56:51浏览次数:60  
标签:case -- image golang int select scase channel

原文:https://blog.csdn.net/cyb_17302190874/article/details/108244683

Go 的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的;当select中没有case语句的时候,会阻塞当前的groutine。所以,有人也会说select是用来阻塞监听goroutine的。
还有人说:select是Golang在语言层面提供的I/O多路复用的机制,其专门用来检测多个channel是否准备完毕:可读或可写。

以上说法都正确。

I/O多路复用

我们来回顾一下是什么是I/O多路复用

普通多线程(或进程)I/O

image

每来一个进程,都会建立连接,然后阻塞,直到接收到数据返回响应。
普通这种方式的缺点其实很明显:系统需要创建和维护额外的线程或进程。因为大多数时候,大部分阻塞的线程或进程是处于等待状态,只有少部分会接收并处理响应,而其余的都在等待。系统为此还需要多做很多额外的线程或者进程的管理工作。

image

为了解决图中这些多余的线程或者进程,于是有了"I/O多路复用"

I/O多路复用

image

每个线程或者进程都先到图中”装置“中注册,然后阻塞,然后只有一个线程在”运输“,当注册的线程或者进程准备好数据后,”装置“会根据注册的信息得到相应的数据。从始至终kernel只会使用图中这个黄黄的线程,无需再对额外的线程或者进程进行管理,提升了效率。

select组成结构

select的实现经历了多个版本的修改,当前版本为:1.11
select这个语句底层实现实际上主要由两部分组成:case语句执行函数
源码地址为:/go/src/runtime/select.go

每个case语句,单独抽象出以下结构体:

 

  1.   type scase struct {
  2.   c *hchan // chan
  3.   elem unsafe.Pointer // 读或者写的缓冲区地址
  4.   kind uint16 //case语句的类型,是default、传值写数据(channel <-) 还是 取值读数据(<- channel)
  5.   pc uintptr // race pc (for race detector / msan)
  6.   releasetime int64
  7.   }

结构体可以用下图表示:

 

image


其中比较关键的是:hchan,它是channel的指针。
在一个select中,所有的case语句会构成一个scase结构体的数组。

 

image

然后执行select语句实际上就是调用func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)函数。

image

func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)函数参数:

  • cas0 为上文提到的case语句抽象出的结构体scase数组的第一个元素地址
  • order0为一个两倍cas0数组长度的buffer,保存scase随机序列pollorder和scase中channel地址序列lockorder。
  • nncases表示scase数组的长度

selectgo返回所选scase的索引(该索引与其各自的select {recv,send,default}调用的序号位置相匹配)。此外,如果选择的scase是接收操作(recv),则返回是否接收到值。

谁负责调用func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)函数呢?

/reflect/value.go中有个func rselect([]runtimeSelect) (chosen int, recvOK bool)函数,此函数的实现在/runtime/select.go文件中的func reflect_rselect(cases []runtimeSelect) (int, bool)函数中:

 

  1.   func reflect_rselect(cases []runtimeSelect) (int, bool) {
  2.   //如果cases语句为空,则阻塞当前groutine
  3.   if len(cases) == 0 {
  4.   block()
  5.   }
  6.   //实例化case的结构体
  7.   sel := make([]scase, len(cases))
  8.   order := make([]uint16, 2*len(cases))
  9.   for i := range cases {
  10.   rc := &cases[i]
  11.   switch rc.dir {
  12.   case selectDefault:
  13.   sel[i] = scase{kind: caseDefault}
  14.   case selectSend:
  15.   sel[i] = scase{kind: caseSend, c: rc.ch, elem: rc.val}
  16.   case selectRecv:
  17.   sel[i] = scase{kind: caseRecv, c: rc.ch, elem: rc.val}
  18.   }
  19.   if raceenabled || msanenabled {
  20.   selectsetpc(&sel[i])
  21.   }
  22.   }
  23.   return selectgo(&sel[0], &order[0], len(cases))
  24.   }

那谁调用的func rselect([]runtimeSelect) (chosen int, recvOK bool)呢?
/refect/value.go中,有一个func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)的函数,其调用了rselect函数,并将最终Go中select语句的返回值的返回。

以上这三个函数的调用栈按顺序如下:

  • func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
  • func rselect([]runtimeSelect) (chosen int, recvOK bool)
  • func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)

这仨函数中无论是返回值还是参数都大同小异,可以简单粗暴的认为:函数参数传入的是case语句,返回值返回被选中的case语句。
那谁调用了func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)呢?
可以简单的认为是系统了。
来个简单的图:

image

 

前两个函数Selectrselect都是做了简单的初始化参数,调用下一个函数的操作。select真正的核心功能,是在最后一个函数func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)中实现的。

selectgo函数做了什么

打乱传入的case结构体顺序

image

锁住其中的所有的channel

 

image

遍历所有的channel,查看其是否可读或者可写

image

如果其中的channel可读或者可写,则解锁所有channel,并返回对应的channel数据

image

image

假如没有channel可读或者可写,但是有default语句,则同上:返回default语句对应的scase并解锁所有的channel。

image

假如既没有channel可读或者可写,也没有default语句,则将当前运行的groutine阻塞,并加入到当前所有channel的等待队列中去。

image

然后解锁所有channel,等待被唤醒。

 

image

此时如果有个channel可读或者可写ready了,则唤醒,并再次加锁所有channel,

 

image

遍历所有channel找到那个对应的channel和G,唤醒G,并将没有成功的G从所有channel的等待队列中移除。

image

如果对应的scase值不为空,则返回需要的值,并解锁所有channel

image

如果对应的scase为空,则循环此过程。

select和channel之间的关系

在想想select和channel做了什么事儿,我觉得和多路复用是一回事儿

 

标签:case,--,image,golang,int,select,scase,channel
From: https://www.cnblogs.com/liujiacai/p/17095904.html

相关文章

  • 1 9 个视图子类 、2 视图集、3 路由系统、4 认证组件
    目录19个视图子类2视图集2.1通过ModelViewSet编写5个接口2.2通过ReadOnlyModelViewSet编写2个只读接口2.3ViewSetMixin源码分析2.4fromrest_framework.viewsets包......
  • 循环删除list元素
    最近和某个朋友聊天,说他手下的一个开发,工作3年多了,一个需求的技术点,需要循环删除List中的元素,整了半天,说程序报错,不会弄。。他挺无语的,和我倾诉,我说工作3年多......
  • XSS Challenges
    XSS挑战(由yamagata21)-阶段#1(int21h.jp)题目要求注入JavaScript命令:alert(document.domain);Stage#1输入321来定位代码的位置,发现是处于<b></b>标签之内,没有......
  • 9 个视图子类、视图集、 路由系统、认证组件
    目录1.9个视图子类2视图集2.1通过ModelViewSet编写5个接口2.3ViewSetMixin源码分析2.4fromrest_framework.viewsets包下的类2.5视图层大总结3路由系统3.1自动生成......
  • 空 struct 占用大小
    问题今天在写头文件的时候,定义一个通讯协议使用的struct,为了协议整体的一致性,在内部嵌套了一个空的struct,按照以往用c写的通讯协议,这样是没什么问题的。结果在计算大......
  • 谷歌是如何改进 GKE、Cloud Run 的 gVisor 文件系统性能的?
    灵活的应用程序架构、CI/CD管道和容器工作负载通常运行不受信任的代码,因此应该与敏感的基础设施隔离。一种常见的解决方案是部署纵深防御产品(如使用gVisor的GKESandbox)以......
  • drf-5
    反序列化类校验分源码解析(了解)反序列化校验,什么时候,开始执行校验(切入点)-视图类中的ser.is_valid(),就会执行校验,校验通过返回True,不通过返回False入口:ser.is_val......
  • Linux 多进程程序调试实例(六)-- 共享内存
    共享内存介绍共享内存就是两个不相关的进程访问同一个逻辑内存,从而达到两个进程互相通信的效果。共享内存是两个正在运行的进程之间共享和传递数据最有快的一种通信方式......
  • centos查找已安装的jdk路径的方法
    在可执行java命令的情况下查找过程如下:执行whichjava[root@localhost~]#whichjava/usr/bin/java执行ls-lrt/usr/bin/java[root@localhost~]#ls-lr......
  • mdbcluster目标构想及达成情况
    mdbcluster目标构想及达成情况  经过漫长研发(主要是人力欠缺),目前mdbcluster已经达到一个准商用标准。先回顾下之前定下的目标,以及现在达成的状态。目标构想......