select case语句_图解Go select语句原理
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)
呢? 可以简单的认为是系统了。 来个简单的图:
前两个函数 Select
和 rselect
都是做了简单的初始化参数,调用下一个函数的操作。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语句原理相关推荐
- oracle语句转成mysql语句_如何监控Mysql语句
快速阅读 为什么要监控sql语句,以及如何监控,都有哪几种方式可以监控. 我们知道sql server 中有个工具叫sql profile ,可以实时监控sql server中 执行的sql 语句,以 ...
- mysql的tcl语句_初识数据库(TCL语句)
TCL语句 : 事物控制语句 --什么是事物 : 多种操作能够达到统一的结果 --在网上购买了一部电话 --1.查询是否有该电话的库存 ,将库存 - 1 --2.从银行卡中扣钱,a)查询卡中的钱是否足 ...
- excel mysql 参数查询语句_如何用SQL语句查询Excel数据?
如何用SQL语句查询Excel数据? Q:如何用SQL语句查询Excel数据? A:下列语句可在SQL SERVER中查询Excel工作表中的数据. 2007和2010版本: SELECT*FROMO ...
- excel mysql 参数查询语句_如何用SQL语句查询Excel数据
Q:如何用SQL语句查询Excel数据? A:下列语句可在SQL SERVER中查询Excel工作表中的数据. 2007和2010版本: SELECT * FROM OpenDataSource( ' ...
- python有几种循环语句_[14] Python循环语句(一)
1. 概述 今天我们介绍循环语句,和条件判断一样,我们从流程图开始看起.首先看一下学习计划列表,粗体为已学,斜体为新增或修改内容.计算机编程的原理简要介绍 集成开发环境PyCharm 变量名.数字.字 ...
- jq select 修改选中_「jquery select」jquery操作select(取值,设置选中) - seo实验室
jquery select 最近工作中总出现select 和 option问题,整理一下,内容大部分源于网络资料 一.基础取值问题 例如 1.设置value为pxx的项选中 $(".sele ...
- select case语句举例_图解Go select语句原理
Go 的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的:当select中没有case语句的时候,会阻塞当前的groutine.所以,有人也会说select是 ...
- mysql select 所有表_怎样用SQL语句查询一个数据库中的所有表
展开全部 查询32313133353236313431303231363533e59b9ee7ad9431333431356639一个数据库中的所有表sql语句是show tables: 显示所有数据 ...
- mysql中的级联删除的语句_级联删除sql语句-数据库级联删除语句-sql删除语句
SQl语句的级联删除问题 删除应该有顺序 1,删除link表 delete from ref,link where ref.link_code=link.link_code and link_id=? ...
最新文章
- 58姚劲波:从不裁员,只有淘汰
- 我建议你了解一点儿Serverless
- Py之docx:Python库之docx简介、安装、使用方法详细攻略
- php v9开发网站,phpcms开发步骤
- 行上下移动_这有一台你迟早要用到的手持式“移动空调”
- SSIS [大容量插入任务] 找不到文件错误
- java 获取date的时分秒_Java Date获取 年月日时分秒
- 机器学习基础(五十三)—— 精确率与召回率(多分类问题精确率和召回率的计算)
- 15.深入分布式缓存:从原理到实践 --- 同程凤凰缓存系统基于Redis的设计与实践
- 12 EDA技术实用教程【时序电路Verilog设计3】
- stringbuffer用法 java_StringBuffer的用法
- Java| Javadoc生成Java帮助文档
- 数据库系统概念 第二章 习题答案
- struct termios结构体详解
- Qcon演讲纪实:详解如何在实时视频通话中实现AR功能
- 输入法规则(U模式输入)
- 键盘过滤驱动之IRP劫持
- 【lwIP(第一章)】lwIP入门
- FL Studio教程之如何外接设备
- 走进C++程序世界-----指针(动态申请空间和释放空间)
热门文章
- linux 线程异常退出_Linux 进程必知必会
- 3 编程基础 Makefile
- 在Linux系统上安装Spring boot应用
- session 、cookie、token的区别
- Linux 系统下 /etc/group 档案结构
- 【新闻发布系统】项目文档
- Nginx(八)-- 负载均衡
- mysql,oracle表数据相互导入
- javaScript = == ===的区别
- BestCoder Round #81 (div.2) B Matrix