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

以上说法都正确。

I/O多路复用

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

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

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

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

I/O多路复用

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

select组成结构

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

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

type scase struct {

c *hchan // chan

elem unsafe.Pointer // 读或者写的缓冲区地址

kind uint16 //case语句的类型,是default、传值写数据(channel

pc uintptr // race pc (for race detector / msan)

releasetime int64

}

结构体可以用下图表示:

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

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

func selectgo(cas0*scase,order0*uint16,ncasesint)(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,ncasesint)(int,bool)函数呢?

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

func reflect_rselect(cases []runtimeSelect) (int, bool) {

//如果cases语句为空,则阻塞当前groutine

if len(cases) == 0 {

block()

}

//实例化case的结构体

sel := make([]scase, len(cases))

order := make([]uint16, 2*len(cases))

for i := range cases {

rc := &cases[i]

switch rc.dir {

case selectDefault:

sel[i] = scase{kind: caseDefault}

case selectSend:

sel[i] = scase{kind: caseSend, c: rc.ch, elem: rc.val}

case selectRecv:

sel[i] = scase{kind: caseRecv, c: rc.ch, elem: rc.val}

}

if raceenabled || msanenabled {

selectsetpc(&sel[i])

}

}

return selectgo(&sel[0], &order[0], len(cases))

}

那谁调用的 func rselect([]runtimeSelect)(chosenint,recvOKbool)呢? 在 /refect/value.go中,有一个 funcSelect(cases[]SelectCase)(chosenint,recvValue,recvOKbool)的函数,其调用了 rselect函数,并将最终Go中select语句的返回值的返回。

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

  • funcSelect(cases[]SelectCase)(chosenint,recvValue,recvOKbool)

  • func rselect([]runtimeSelect)(chosenint,recvOKbool)

  • func selectgo(cas0*scase,order0*uint16,ncasesint)(int,bool)

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

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

selectgo函数做了什么

打乱传入的case结构体顺序

锁住其中的所有的channel

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

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

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

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

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

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

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

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

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

select和channel之间的关系

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

更多精彩内容,请关注我的微信公众号 互联网技术窝 或者加微信共同探讨交流:

参考文献:

  • https://my.oschina.net/renhc/blog/2253937

  • https://blog.csdn.net/xdrbt/article/details/80287959

  • https://blog.csdn.net/qq_34199383/article/details/80303629

  • https://blog.csdn.net/wangxindong11/article/details/78591308

  • https://draveness.me/golang-select

  • https://studygolang.com/articles/1807

select case语句_图解Go select语句原理相关推荐

  1. oracle语句转成mysql语句_如何监控Mysql语句

    快速阅读 为什么要监控sql语句,以及如何监控,都有哪几种方式可以监控. 我们知道sql server 中有个工具叫sql profile ,可以实时监控sql server中 执行的sql 语句,以 ...

  2. mysql的tcl语句_初识数据库(TCL语句)

    TCL语句 : 事物控制语句 --什么是事物 : 多种操作能够达到统一的结果 --在网上购买了一部电话 --1.查询是否有该电话的库存 ,将库存 - 1 --2.从银行卡中扣钱,a)查询卡中的钱是否足 ...

  3. excel mysql 参数查询语句_如何用SQL语句查询Excel数据?

    如何用SQL语句查询Excel数据? Q:如何用SQL语句查询Excel数据? A:下列语句可在SQL SERVER中查询Excel工作表中的数据. 2007和2010版本: SELECT*FROMO ...

  4. excel mysql 参数查询语句_如何用SQL语句查询Excel数据

    Q:如何用SQL语句查询Excel数据? A:下列语句可在SQL SERVER中查询Excel工作表中的数据. 2007和2010版本: SELECT * FROM OpenDataSource( ' ...

  5. python有几种循环语句_[14] Python循环语句(一)

    1. 概述 今天我们介绍循环语句,和条件判断一样,我们从流程图开始看起.首先看一下学习计划列表,粗体为已学,斜体为新增或修改内容.计算机编程的原理简要介绍 集成开发环境PyCharm 变量名.数字.字 ...

  6. jq select 修改选中_「jquery select」jquery操作select(取值,设置选中) - seo实验室

    jquery select 最近工作中总出现select 和 option问题,整理一下,内容大部分源于网络资料 一.基础取值问题 例如 1.设置value为pxx的项选中 $(".sele ...

  7. select case语句举例_图解Go select语句原理

    Go 的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的:当select中没有case语句的时候,会阻塞当前的groutine.所以,有人也会说select是 ...

  8. mysql select 所有表_怎样用SQL语句查询一个数据库中的所有表

    展开全部 查询32313133353236313431303231363533e59b9ee7ad9431333431356639一个数据库中的所有表sql语句是show tables: 显示所有数据 ...

  9. mysql中的级联删除的语句_级联删除sql语句-数据库级联删除语句-sql删除语句

    SQl语句的级联删除问题 删除应该有顺序 1,删除link表 delete from ref,link where ref.link_code=link.link_code and link_id=? ...

最新文章

  1. 58姚劲波:从不裁员,只有淘汰
  2. 我建议你了解一点儿Serverless
  3. Py之docx:Python库之docx简介、安装、使用方法详细攻略
  4. php v9开发网站,phpcms开发步骤
  5. 行上下移动_这有一台你迟早要用到的手持式“移动空调”
  6. SSIS [大容量插入任务] 找不到文件错误
  7. java 获取date的时分秒_Java Date获取 年月日时分秒
  8. 机器学习基础(五十三)—— 精确率与召回率(多分类问题精确率和召回率的计算)
  9. 15.深入分布式缓存:从原理到实践 --- 同程凤凰缓存系统基于Redis的设计与实践
  10. 12 EDA技术实用教程【时序电路Verilog设计3】
  11. stringbuffer用法 java_StringBuffer的用法
  12. Java| Javadoc生成Java帮助文档
  13. 数据库系统概念 第二章 习题答案
  14. struct termios结构体详解
  15. Qcon演讲纪实:详解如何在实时视频通话中实现AR功能
  16. 输入法规则(U模式输入)
  17. 键盘过滤驱动之IRP劫持
  18. 【lwIP(第一章)】lwIP入门
  19. FL Studio教程之如何外接设备
  20. 走进C++程序世界-----指针(动态申请空间和释放空间)

热门文章

  1. linux 线程异常退出_Linux 进程必知必会
  2. 3 编程基础 Makefile
  3. 在Linux系统上安装Spring boot应用
  4. session 、cookie、token的区别
  5. Linux 系统下 /etc/group 档案结构
  6. 【新闻发布系统】项目文档
  7. Nginx(八)-- 负载均衡
  8. mysql,oracle表数据相互导入
  9. javaScript = == ===的区别
  10. BestCoder Round #81 (div.2) B Matrix