利志分享
fast_forward
view_headline
开发工具箱
go教程
clickhouse教程
kafka教程
python教程
shell教程
原创杂文
打赏
开发工具箱
go教程
clickhouse教程
kafka教程
python教程
shell教程
原创杂文
打赏
go基础知识
go的环境搭建
go变量
go常量
go字符串
go数组和切片
go的map和range的使用
go的struct的使用
go的函数使用
go的interface的使用
go channel使用
go的routine使用
go的panic和recover使用
go实现http请求
go 复杂的http请求
go实现表单提交
go实现表单验证
go上传附件
go实现mysql连接
go实现redis操作
go对xml操作
go的json操作
go的base64使用
go实现websocket功能
go的单元测试
go的文件操作
go的web服务基础
golang url解析和包介绍使用
go的正则表达式-MatchString,FindString等的使用
golang实现从byte和文件中读取csv格式数据
go进阶
go的类型转换
go的map的多维应用
go的多维数组和slice使用
go的select使用
go的原子性atomic类库使用
go给图片添加水印
go给图片添加文字
go实现http的rpc服务
go实现tcp的rpc服务
go实现json格式的rpc服务
多个defer的执行问题
golang的队列机制实现同步主线程接受子协程的结果
go的值传递和引用传递以及引用类型的问题
go中的make和new的使用问题
golang读文件分析1
golang读文件分析2
golang实现自然周计算
golang实现读写excel
go实战
beego的安装和使用
beego聊天室的基本配置
beego聊天室的生成
Go 写一个类似 cron 的定时任务管理器
Go 调度器 M, P 和 G
AES对称加密算法如何用golang语言实现?
非对称加密的RSA算法如何通过golang来实现?
golang实现http2.0服务端,客户端完整案例
go实战总结
go的日期操作类使用-日常使用类库no.1
go的字符串的连接讲解-日常实战总结no.1
golang实现队列服务-日常实战总结no.2
深入理解golang的channel的使用-日常实战总结no.3
go的sync.pool在实际应用中的讲解和性能分析比较-日常实战总结no.4
go语言中一个典型的引用类型的数据使用案例的注意点-日常实战总结no.5
go的sync包的使用详解1-日常实战总结6
go的sync包的使用详解2-日常实战总结7
深度学习go判断各个类型相等-日常实战总结8
go的排序类使用讲解-日常实战总结9
go的context使用讲解
golang 网络爬虫框架gocolly
golang实现桶排序
golang处理gb2312转utf-8编码的问题
golang实现单链的添加,删除以及翻转
golang的一个err不判断引起的血案(json.Marshal的error到底要不要判断?)
如何控制golang协程的并发数量问题-panic: too many concurrent operations on a single file or socket (max 1048575)
你所要知道的redis客户端返回值知识点
golang实现连续的时间,比如连续的天,月,年等。
go深入
由引用类型引发的概念的深入理解
sync.WaitGroup深入源码理解
golang如何创建动态的struct类型以及如何转换成slice类型
深入理解go的管道数据读写
关于go的只读管道只写管道以及单向管道的理解
深入理解go的slice深入,slice扩容机制
深入理解go的函数参数传递
golang实现动态调用不同struct中不同的方法
如何配置sqlx.DB的SetMaxOpenConns SetMaxIdleConns 和 SetConnMaxLifetime来保证更好的性能
深入理解go的select原理
深入理解golang的GPM模型
精通golang的项目管理go modules
深入理解golang的GC回收机制
超级肝文-深入剖析客户端出现connect reset by peer报错相关的技术知识
Golang源码深入-Go1.15.6发起http请求流程-1
Golang源码深入-Go1.15.6发起http请求流程-2
Golang源码深入-Go1.15.6发起http请求流程-3(http2)
go应用
需求整理-手把手带大家用go开发一个匿名在线聊天室
第二篇-手把手带大家用go开发一个匿名在线聊天室
第三篇-手把手带大家用go开发一个匿名在线聊天室
go面试
【建议收藏】吐血整理Golang面试干货21问-吊打面试官-1
【建议收藏】整理Golang面试第二篇干货13问
【建议收藏】Redis知识干货汇总
【建议收藏】Mysql知识干货(mysql八股文)汇总
目录
go基础知识
go的环境搭建
go变量
go常量
go字符串
go数组和切片
go的map和range的使用
go的struct的使用
go的函数使用
go的interface的使用
go channel使用
go的routine使用
go的panic和recover使用
go实现http请求
go 复杂的http请求
go实现表单提交
go实现表单验证
go上传附件
go实现mysql连接
go实现redis操作
go对xml操作
go的json操作
go的base64使用
go实现websocket功能
go的单元测试
go的文件操作
go的web服务基础
golang url解析和包介绍使用
go的正则表达式-MatchString,FindString等的使用
golang实现从byte和文件中读取csv格式数据
go进阶
go的类型转换
go的map的多维应用
go的多维数组和slice使用
go的select使用
go的原子性atomic类库使用
go给图片添加水印
go给图片添加文字
go实现http的rpc服务
go实现tcp的rpc服务
go实现json格式的rpc服务
多个defer的执行问题
golang的队列机制实现同步主线程接受子协程的结果
go的值传递和引用传递以及引用类型的问题
go中的make和new的使用问题
golang读文件分析1
golang读文件分析2
golang实现自然周计算
golang实现读写excel
go实战
beego的安装和使用
beego聊天室的基本配置
beego聊天室的生成
Go 写一个类似 cron 的定时任务管理器
Go 调度器 M, P 和 G
AES对称加密算法如何用golang语言实现?
非对称加密的RSA算法如何通过golang来实现?
golang实现http2.0服务端,客户端完整案例
go实战总结
go的日期操作类使用-日常使用类库no.1
go的字符串的连接讲解-日常实战总结no.1
golang实现队列服务-日常实战总结no.2
深入理解golang的channel的使用-日常实战总结no.3
go的sync.pool在实际应用中的讲解和性能分析比较-日常实战总结no.4
go语言中一个典型的引用类型的数据使用案例的注意点-日常实战总结no.5
go的sync包的使用详解1-日常实战总结6
go的sync包的使用详解2-日常实战总结7
深度学习go判断各个类型相等-日常实战总结8
go的排序类使用讲解-日常实战总结9
go的context使用讲解
golang 网络爬虫框架gocolly
golang实现桶排序
golang处理gb2312转utf-8编码的问题
golang实现单链的添加,删除以及翻转
golang的一个err不判断引起的血案(json.Marshal的error到底要不要判断?)
如何控制golang协程的并发数量问题-panic: too many concurrent operations on a single file or socket (max 1048575)
你所要知道的redis客户端返回值知识点
golang实现连续的时间,比如连续的天,月,年等。
go深入
由引用类型引发的概念的深入理解
sync.WaitGroup深入源码理解
golang如何创建动态的struct类型以及如何转换成slice类型
深入理解go的管道数据读写
关于go的只读管道只写管道以及单向管道的理解
深入理解go的slice深入,slice扩容机制
深入理解go的函数参数传递
golang实现动态调用不同struct中不同的方法
如何配置sqlx.DB的SetMaxOpenConns SetMaxIdleConns 和 SetConnMaxLifetime来保证更好的性能
深入理解go的select原理
深入理解golang的GPM模型
精通golang的项目管理go modules
深入理解golang的GC回收机制
超级肝文-深入剖析客户端出现connect reset by peer报错相关的技术知识
Golang源码深入-Go1.15.6发起http请求流程-1
Golang源码深入-Go1.15.6发起http请求流程-2
Golang源码深入-Go1.15.6发起http请求流程-3(http2)
go应用
需求整理-手把手带大家用go开发一个匿名在线聊天室
第二篇-手把手带大家用go开发一个匿名在线聊天室
第三篇-手把手带大家用go开发一个匿名在线聊天室
go面试
【建议收藏】吐血整理Golang面试干货21问-吊打面试官-1
【建议收藏】整理Golang面试第二篇干货13问
【建议收藏】Redis知识干货汇总
【建议收藏】Mysql知识干货(mysql八股文)汇总
深入理解go的select原理
阅读:428
分享次数:0
**1:简介** - go的select为golang提供了多路IO复用机制,和其他IO复用一样,用于检测是否有读写事件是否ready。linux的系统IO模型有select,poll,epoll,go的select和linux系统select非常相似。 **2:初识** - select操作至少要有一个case语句,并且不能出现读写nil的channel,否则会报错。 - select仅支持管道,而且是单协程操作 - 每个case语句仅能处理一个管道,要么读要么写 - 多个case语句的执行顺序是随机的 - 存在default语句,select将不会阻塞,但是存在default会影响性能。 ------------ 没有case的select或者case为nil的select package main func main() { select {} } package main import "fmt" func main() { var ch chan int select { case ch <- 1: fmt.Println("222") } } 上面的代码均会有如下报错: fatal error: all goroutines are asleep - deadlock! goroutine 1 [select (no cases)]: main.main() E:/Server/go/src/any.com/test/t_50.go:8 +0x27 ------------ select仅能操作管道 下面是示例代码: package main import "fmt" func main() { tmp := 3 select { case tmp == 3: fmt.Println("222") } } 执行之后报错如下: .\t_50.go:8:11: tmp == 3 evaluated but not used .\t_50.go:8:11: select case must be receive, send or assign recv ------------ select每次执行只能有一个case执行,而且每个case可能执行的条件都是随机的,下面我们看个例子,当然这个例子是无法证明这个,后面我们会对源码进行分析讲解。 package main import ( "fmt" "time" ) func main() { //每秒执行一个ticker t1 := time.NewTicker(1 * time.Second) //每5秒执行一个ticker t2 := time.NewTicker(5 * time.Second) //设置一个通道 ch := make(chan int, 1) for { select { case <-t1.C: fmt.Println("t1") for i := 0; i <= 3; i++ { time.Sleep(6 * time.Second) fmt.Println(i) } case <-t2.C: fmt.Println("t2") case ch <- 1: fmt.Println("ch写入成功") } } } 看下执行结果,都是按照每个case都可能执行到,而且顺序是不确定的。 ch写入成功 t1 0 1 2 3 t2 t1 0 1 2 3 t2 t1 0 1 2 3 t2 **3:深入源码** - select结构组成主要是由case语句和执行的函数组成。 我这里的go的版本是1.15.6 每个case语句的数据结构如下: // Changes here must also be made in src/cmd/internal/gc/select.go's scasetype. type scase struct { c *hchan // chan的结构体类型指针 elem unsafe.Pointer //读或者写的缓冲区地址 element kind uint16 //case语句的类型,是default、传值写数据(channel <-) 还是 取值读数据(<- channel) pc uintptr // 竞争检查相关用的,go run -race指令 releasetime int64 } 在一个select中,所有的case语句一起组成scase的结构数组,然后执行select语句就是调用func selectgo(cas0 *scase, order0 *uint16, ncases int)函数。 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}调用的序号位置相匹配),selectgo做了打乱case的结构体顺序,然后会随机选一个执行。如果选择的scase是接收操作(recv),则返回是否接收到值。 selectgo的调用是通过reflect_rselect函数来调用的,reflect_rselect函数的方法是rselect的实现,rselect声明是在/reflect/value.go rselect的调用是通过/reflect/value.go里面的Select方法来调用的。 关于Select方法的调用当然就是系统来调用了。 关于select语句的底层函数调用流程我们做一下总结: - 系统调用func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) - 然后Select调用func rselect([]runtimeSelect) (chosen int, recvOK bool) - rselect又调用func reflect_rselect(cases []runtimeSelect) (int, bool) - 最后调用到func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 下面我们来深入看下selectgo的函数,由于selectgo函数比较长,我这里大概列一下重要的点: 对所有的case进行排序,生成随机顺序,代码如下: // generate permuted order for i := 1; i < ncases; i++ { j := fastrandn(uint32(i + 1)) pollorder[i] = pollorder[j] pollorder[j] = uint16(i) } 遍历所有的case语句,如果所有的case都未就绪,则走default,如果没有default,则会阻塞。如果有就绪channel,则直接跳出顺序进行管道操作并返回。核心代码如下: loop: // pass 1 - look for something already waiting var dfli int var dfl *scase var casi int var cas *scase var recvOK bool for i := 0; i < ncases; i++ { casi = int(pollorder[i]) cas = &scases[casi] c = cas.c switch cas.kind { case caseNil: continue case caseRecv: sg = c.sendq.dequeue() if sg != nil { goto recv } if c.qcount > 0 { goto bufrecv } if c.closed != 0 { goto rclose } case caseSend: if raceenabled { racereadpc(c.raceaddr(), cas.pc, chansendpc) } if c.closed != 0 { goto sclose } sg = c.recvq.dequeue() if sg != nil { goto send } if c.qcount < c.dataqsiz { goto bufsend } case caseDefault: dfli = casi dfl = cas } } if dfl != nil { selunlock(scases, lockorder) casi = dfli cas = dfl goto retc } retc: if cas.releasetime > 0 { blockevent(cas.releasetime-t0, 1) } return casi, recvOK **4:总结多路复用** 我们看下select中的channel实现多路复用的图: 每个线程或者进程都先到图中”装置“中注册,然后阻塞,然后只有一个线程在”运输“,当注册的线程或者进程准备好数据后,”装置“会根据注册的信息得到相应的数据。从始至终kernel只会使用图中这个黄黄的线程,无需再对额外的线程或者进程进行管理,提升了效率。我这里就没有提协程了,因为最终还是系统线程去执行。
感觉本站内容不错,读后有收获?
attach_money
我要小额打赏,鼓励作者写出更好的教程
扫码关注公众号:talk_lizhi