深入浅出Golang中select的实现原理
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《深入浅出Golang中select的实现原理》,聊聊select,我们一起来看看吧!
概述
在go语言中,select语句就是用来监听和channel有关的IO操作,当IO操作发生时,触发相应的case操作,有了select语句,可以实现main主线程与goroutine线程之间的互动。需要的朋友可以参考以下内容,希望对大家有帮助。
select实现原理
Golang实现select时,定义了一个数据结构表示每个case语句(包含default,default实际上是一种特殊的case),select执行过程可以看成一个函数,函数输入case数组,输出选中的case,然后程序流程转到选中的case块。
执行流程

在默认的情况下,select 语句会在编译阶段经过如下过程的处理:
- 将所有的
case转换成包含Channel以及类型等信息的 scase 结构体; - 调用运行时函数
selectgo获取被选择的scase结构体索引,如果当前的scase是一个接收数据的操作,还会返回一个指示当前case是否是接收的布尔值; - 通过
for循环生成一组if语句,在语句中判断自己是不是被选中的case。
case数据结构
select控制结构中case使用了scase结构体来表示,源码包src/runtime/select.go:scase定义了表示case语句的数据结构:
type scase struct {
c *hchan
elem unsafe.Pointer
kind uint16
pc uintptr
releasetime int64
}
scase.c:由于非default的case中都与channel的发送和接收数据有关,所以在scase结构体中也包含一个c字段用于存储case中使用的channel,为当前case语句所操作的channel指针,这也说明了一个case语句只能操作一个channel。
scase.kind:表示该case的类型,分为读channel、写channel和default,三种类型分别由常量定义:
const (
caseNil = iota
caseRecv //case语句中尝试读取scase.c中的数据;
caseSend //case语句中尝试向scase.c中写入数据;
caseDefault //default语句
)
scase.elem:用于接收或者发送数据的变量地址,根据scase.kind不同,有不同的用途:
- scase.kind == caseRecv :
scase.elem表示读出channel的数据存放地址; - scase.kind == caseSend :
scase.elem表示将要写入channel的数据存放地址;
执行select
在运行期间会调用selectgo()函数,这个函数主要作用是从select控制结构中的多个case中选择一个需要执行的case,随后的多个 if 条件语句就会根据 selectgo() 的返回值执行相应的语句。
运行时源码包src/runtime/select.go:selectgo()定义了select选择case的函数:
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
cas1 := (*[1
<p><code>selectgo</code> 函数首先会进行执行一些初始化操作,也就是决定处理 <code>case </code>的两个顺序,其中一个是 <code>pollOrder </code>另一个是 <code>lockOrder</code>。</p>
<p>函数参数:</p>
- cas0:为scase数组的首地址,
selectgo()就是从这些scase中找出一个返回。 - order0:为一个两倍cas0数组长度的buffer,保存scase随机序列
pollorder和scase中channel地址序列lockorder; - pollorder:每次
selectgo执行都会把scase序列打乱,以达到随机检测case的目的。 - lockorder:所有
case语句中channel序列,以达到去重防止对channel加锁时重复加锁的目的。 - ncases:表示
scase数组的长度
函数返回值:
- int: 选中
case的编号,这个case编号跟代码一致 - bool: 是否成功从
channle中读取了数据,如果选中的case是从channel中读数据,则该返回值表示是否读取成功。
循环
当 select 语句确定了轮询和锁定的顺序并锁定了所有的 Channel 之后就会开始进入 select的主循环,查找或者等待 Channel 准备就绪,循环中会遍历所有的 case 并找到需要被唤起的sudog 结构体。
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
// ...
gp = getg()
nextp = &gp.waiting
for _, casei := range lockorder {
casi = int(casei)
cas = &scases[casi]
if cas.kind == caseNil {
continue
}
c = cas.c
sg := acquireSudog()
sg.g = gp
sg.isSelect = true
sg.elem = cas.elem
sg.c = c
*nextp = sg
nextp = &sg.waitlink
switch cas.kind {
case caseRecv:
c.recvq.enqueue(sg)
case caseSend:
c.sendq.enqueue(sg)
}
}
gp.param = nil
gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)
// ...
}
在这段循环的代码中,我们会分四种不同的情况处理 select 中的多个 case:
caseNil — 当前 case 不包含任何的 Channel,就直接会被跳过;
caseRecv — 当前 case 会从 Channel 中接收数据;
- 如果当前
Channel的sendq上有等待的Goroutine就会直接跳到recv标签所在的代码段,从Goroutine中获取最新发送的数据; - 如果当前
Channel的缓冲区不为空就会跳到bufrecv标签处从缓冲区中获取数据; - 如果当前
Channel已经被关闭就会跳到rclose做一些清除的收尾工作;
caseSend — 当前 case 会向 Channel 发送数据;
- 如果当前
Channel已经被关闭就会直接跳到rclose代码段; - 如果当前
Channel的recvq上有等待的Goroutine就会跳到send代码段向Channel直接发送数据;
caseDefault — 表示默认情况,如果循环执行到了这种情况就表示前面的所有case 都没有被执行,所以这里会直接解锁所有的 Channel 并退出 selectgo 函数,这时也就意味着当前 select 结构中的其他收发语句都是非阻塞的。
总结
通过以上内容我们简单的了解了select结构的执行过程与实现原理,首先在编译期间,Go 语言会对 select 语句进行优化, 对于空的select语句会直接转换成block函数的调用,直接挂起当前Goroutine,如果select语句中只包含一个case,就会被转换成if ch == nil {block}; n; 表达式。然后执行case结构体中内容。
在运行时会执行selectgo函数,随机生成一个遍历的轮询顺序pollOrder并根据Channel地址生成一个用于遍历的锁定顺序lockOrder;然后根据pollOrder遍历所有的case查看是否有可以处理的Channel消息,如果有消息就直接获取case对应的索引并返回。如果没有消息就会创建sudog结构体,将当前 Goroutine 加入到所有相关 Channel 的sendq 和 recvq 队列中并调用 gopark 触发调度器的调度;
注意: 并不是所有的select控制结构都会走到selectgo,很多情况都会被直接优化调。
到这里,我们也就讲完了《深入浅出Golang中select的实现原理》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于golang的知识点!
golang中随机数rand的使用
- 上一篇
- golang中随机数rand的使用
- 下一篇
- Golang接入钉钉通知的示例代码
-
- Golang · Go教程 | 2小时前 | 格式化输出 printf fmt库 格式化动词 Stringer接口
- Golangfmt库用法与格式化技巧解析
- 140浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Golang配置Protobuf安装教程
- 147浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Golang中介者模式实现与通信解耦技巧
- 378浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Golang多协程通信技巧分享
- 255浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Golang如何判断变量类型?
- 393浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Golang云原生微服务实战教程
- 310浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Golang迭代器与懒加载结合应用
- 110浏览 收藏
-
- Golang · Go教程 | 3小时前 | 性能优化 并发安全 Golangslicemap 预设容量 指针拷贝
- Golangslicemap优化技巧分享
- 412浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Golang代理模式与访问控制实现解析
- 423浏览 收藏
-
- Golang · Go教程 | 4小时前 |
- Golang事件管理模块实现教程
- 274浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3163次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3375次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3403次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4506次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3784次使用
-
- Go语言面试题之select和channel的用法
- 2022-12-30 477浏览
-
- Go使用select切换协程入门详解
- 2022-12-30 135浏览
-
- Go语言select语句用法示例
- 2023-01-07 185浏览
-
- Go select使用与底层原理讲解
- 2023-01-22 498浏览
-
- golang中select语句的简单实例
- 2022-12-31 424浏览

