图解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 <-) 还是 取值读数据(<- channel)
pc uintptr // race pc (for race detector / msan)
releasetime int64
}
结构体可以用下图表示:

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

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

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)函数中:
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) (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)呢?
可以简单的认为是系统了。
来个简单的图:
前两个函数Select和rselect都是做了简单的初始化参数,调用下一个函数的操作。select真正的核心功能,是在最后一个函数func selectgo(cas0 *scase, order0 *uint16, ncases int) (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做了什么事儿,我觉得和多路复用是一回事儿

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

参考文献:
图解Go select语句原理的更多相关文章
- 从Select语句看Oracle查询原理(了解Oracle的查询机制)
第一步:客户端把语句发给服务器端执行 当我们在客户端执行select语句时,客户端会把这条SQL语句发送给服务器端,让服务器端的进程来处理这语句.也就是说,Oracle客户端是不会做任何的操作,他的主 ...
- 1110MySQL select实现原理
转自http://www.jianshu.com/p/NsWbRv 工作中需要借鉴MySQL对于select的具体实现,在网上搜了很久,几乎都是介绍原理的,对于实现细节都没有介绍,无奈之下只得自己对着 ...
- select查询原理
原文:select查询原理 我并非专业DBA,但做为B/S架构的开发人员,总是离不开数据库,一般开发员只会应用SQL的四条经典语句:select ,insert,delete,update.但是我从来 ...
- Go语言规格说明书 之 select语句(Select statements)
go version go1.11 windows/amd64 本文为阅读Go语言中文官网的规则说明书(https://golang.google.cn/ref/spec)而做的笔记,介绍Go语言的 ...
- Select 语句执行顺序以及如何提高Oracle 基本查询效率
今天把这几天做的练习复习了一下,不知道自己写得代码执行的效率如何以及要如何提高,于是乎上网开始研究一些材料,现整理如下: 首先,要了解在Oracle中Sql语句运行的机制.以下是sql语句的执行步骤: ...
- Oracle 检索数据(查询数据、select语句)
用户对表或视图最常进行的操作就是检索数据,检索数据可以通过 select 语句来实现,该语句由多个子句组成,通过这些子句完成筛选.投影和连接等各种数据操作,最终得到想要的结果. 语法: select ...
- golang的select实现原理剖析
写在最前面 select为golang提供了多路IO复用机制,和其他IO复用一样,用于检测是否有读写事件是否ready. 本文将介绍一下golang的select的用法和实现原理. 实现原理 gola ...
- oracle(sql)基础篇系列(一)——基础select语句、常用sql函数、组函数、分组函数
花点时间整理下sql基础,温故而知新.文章的demo来自oracle自带的dept,emp,salgrade三张表.解锁scott用户,使用scott用户登录就可以看到自带的表. #使用ora ...
- CREATE TABLE 表名 AS SELECT 语句
1.新表不存在复制表结构即数据到新表 ? 1 2 create table new_table select * from old_talbe; 这种方法会将old_table中所有的内容都拷贝过来, ...
随机推荐
- python汉诺塔问题的递归理解
一.问题背景 汉诺塔问题是源于印度一个古老传说. 源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下 ...
- mySQL的表操作
1.新建表 CREATE TABLE 表名 ( 属性名 数据类型 [完整约束条件], 属性名 数据类型 [完整约束条件], ... ... 属性名 数据类型 [完整约束条件] ); 2.删除表 DRO ...
- leetcode1034
class Solution: def __init__(self): self.V = list() def bfs(self,grid,color,rows,coloums,r,c,ocolor) ...
- lanya
var app = getApp() Page({ data: { motto: 'Hello World', openBLE:'打开蓝牙设备', startBLEDiscover ...
- Redis读超时排查
现象:STORM UI中发现bolt中有failed,异常日志抛异常read time out 业务场景:storm读写redis. 查看redis日志发现,正常情况下 每隔2小时都会有超时的异常抛出 ...
- 《DOM Scripting》学习笔记-——第五章、第六章 案列改进
第四章的案例代码可以得到更好的改进.例如:预留退路.向后兼容性和分离js. 原html代码: <!DOCTYPE html> <html lang="en"> ...
- Error:java: Compilation failed: internal java compiler error 解决办法
https://blog.csdn.net/jdjdndhj/article/details/70256989
- css,html备忘录
1.background:url()#000 ;代表当图片不够充满容器时,用黑色填满. 2.height:inherit;继承父级元素的高度. 当子元素给了绝对定位,父元素不给相对定位,子元素也能适配 ...
- [Solution] 969. Pancake Sorting
Difficulty: Medium Problem Given an array A, we can perform a pancake flip: We choose some positive ...
- 获取select下拉框选中的的值
有一id=test的下拉框,怎么获取选中的那个值呢? 分别使用javascript方法和jquery方法 <select id="test" name="&quo ...