《Go语言实战》(威廉·肯尼迪 (William Kennedy))电子书网盘下载免费在线阅读
链接:https://pan.baidu.com/s/1mNt2bRjwacBu3C5YNe-dOQ
提取码:1234书名:Go语言实战
作者:威廉·肯尼迪 (William Kennedy)
译者:李兆海
豆瓣评分:7.7
出版社:人民邮电出版社
出版年份:2017-3-1
页数:224
内容简介:
Go语言结合了底层系统语言的能力以及现代语言的高级特性,旨在降低构建简单、可靠、高效软件的门槛。本书向读者提供一个专注、全面且符合语言习惯的视角。Go语言实战同时关注语言的规范和实现,涉及的内容包括语法、类型系统、并发、管道、测试,以及其他一些主题。
作者简介:
William Kennedy,是一位熟练的软件开发者,也是博客GoingGo.Net的作者。
Brian Ketelsen和Erik St. Martin是全球Go语言大会GopherCon的组织者,也是Go语言框架Skynet的联合作者。
李兆海,多年专注于后端分布式网络服务开发,曾使用过多个流行后端技术和相关架构实践,是Go语言和Docker的早期使用者和推广者,《第1本Docker书》的译者。作为项目技术负责人,成功开发了百万用户级直播系统。
Go 的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的;当select中没有case语句的时候,会阻塞当前的groutine。所以,有人也会说select是用来阻塞监听goroutine的。还有人说:select是Golang在语言层面提供的I/O多路复用的机制,其专门用来检测多个channel是否准备完毕:可读或可写。
以上说法都正确。
我们来回顾一下是什么是 I/O多路复用 。
每来一个进程,都会建立连接,然后阻塞,直到接收到数据返回响应。
普通这种方式的缺点其实很明显:系统需要创建和维护额外的线程或进程。因为大多数时候,大部分阻塞的线程或进程是处于等待状态,只有少部分会接收并处理响应,而其余的都在等待。系统为此还需要多做很多额外的线程或者进程的管理工作。
为了解决图中这些多余的线程或者进程,于是有了"I/O多路复用"
每个线程或者进程都先到图中”装置“中注册,然后阻塞,然后只有一个线程在”运输“,当注册的线程或者进程准备好数据后,”装置“会根据注册的信息得到相应的数据。从始至终kernel只会使用图中这个黄黄的线程,无需再对额外的线程或者进程进行管理,提升了效率。
select的实现经历了多个版本的修改,当前版本为:1.11
select这个语句底层实现实际上主要由两部分组成: case语句 和 执行函数 。
源码地址为:/go/src/runtime/select.go
每个case语句,单独抽象出以下结构体:
结构体可以用下图表示:
然后执行select语句实际上就是调用 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 函数。
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 函数参数:
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 rselect([]runtimeSelect) (chosen int, recvOK bool) 呢?
在 /refect/value.go 中,有一个 func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) 的函数,其调用了 rselect 函数,并将最终Go中select语句的返回值的返回。
以上这三个函数的调用栈按顺序如下:
这仨函数中无论是返回值还是参数都大同小异,可以简单粗暴的认为:函数参数传入的是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) 中实现的。
打乱传入的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做了什么事儿,我觉得和多路复用是一回事儿