go语言多路复用,go 多路复用
为什么要使用 Go 语言?Go 语言的优势在哪里
1、学习曲线
创新互联建站主营林甸网站建设的网络公司,主营网站建设方案,成都app软件开发,林甸h5成都小程序开发搭建,林甸网站营销推广欢迎林甸等地区企业咨询
它包含了类C语法、GC内置和工程工具。这一点非常重要,因为Go语言容易学习,所以一个普通的大学生花一个星期就能写出来可以上手的、高性能的应用。在国内大家都追求快,这也是为什么国内Go流行的原因之一。
2、效率
Go拥有接近C的运行效率和接近PHP的开发效率,这就很有利的支撑了上面大家追求快速的需求。
3、出身名门、血统纯正
之所以说Go语言出身名门,是因为我们知道Go语言出自Google公司,这个公司在业界的知名度和实力自然不用多说。Google公司聚集了一批牛人,在各种编程语言称雄争霸的局面下推出新的编程语言,自然有它的战略考虑。而且从Go语言的发展态势来看,Google对它这个新的宠儿还是很看重的,Go自然有一个良好的发展前途。我们看看Go语言的主要创造者,血统纯正这点就可见端倪了。
4、组合的思想、无侵入式的接口
Go语言可以说是开发效率和运行效率二者的完美融合,天生的并发编程支持。Go语言支持当前所有的编程范式,包括过程式编程、面向对象编程以及函数式编程。
5、强大的标准库
这包括互联网应用、系统编程和网络编程。Go里面的标准库基本上已经是非常稳定,特别是我这里提到的三个,网络层、系统层的库非常实用。
6、部署方便
我相信这一点是很多人选择Go的最大理由,因为部署太方便,所以现在也有很多人用Go开发运维程序。
7、简单的并发
它包含降低心智的并发和简易的数据同步,我觉得这是Go最大的特色。之所以写正确的并发、容错和可扩展的程序如此之难,是因为我们用了错误的工具和错误的抽象,Go可以说这一块做的相当简单。
8、稳定性
Go拥有强大的编译检查、严格的编码规范和完整的软件生命周期工具,具有很强的稳定性,稳定压倒一切。那么为什么Go相比于其他程序会更稳定呢?这是因为Go提供了软件生命周期的各个环节的工具,如go
tool、gofmt、go test。
图解Go中select语句的底层原理
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做了什么事儿,我觉得和多路复用是一回事儿
协程与异步IO
协程,又称微线程,纤程。英文名 Coroutine 。Python对协程的支持是通过 generator 实现的。在generator中,我们不但可以通过for循环来迭代,还可以不断调用 next()函数 获取由 yield 语句返回的下一个值。但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。yield其实是终端当前的函数,返回给调用方。python3中使用yield来实现range,节省内存,提高性能,懒加载的模式。
asyncio是Python 3.4 版本引入的 标准库 ,直接内置了对异步IO的支持。
从Python 3.5 开始引入了新的语法 async 和 await ,用来简化yield的语法:
import asyncio
import threading
async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
print(threading.current_thread().name)
await asyncio.sleep(x + y)
return x + y
async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))
print(threading.current_thread().name)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
tasks = [print_sum(1, 2), print_sum(3, 4)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
线程是内核进行抢占式的调度的,这样就确保了每个线程都有执行的机会。而 coroutine 运行在同一个线程中,由语言的运行时中的 EventLoop(事件循环) 来进行调度。和大多数语言一样,在 Python 中,协程的调度是非抢占式的,也就是说一个协程必须主动让出执行机会,其他协程才有机会运行。
让出执行的关键字就是 await。也就是说一个协程如果阻塞了,持续不让出 CPU,那么整个线程就卡住了,没有任何并发。
PS: 作为服务端,event loop最核心的就是IO多路复用技术,所有来自客户端的请求都由IO多路复用函数来处理;作为客户端,event loop的核心在于利用Future对象延迟执行,并使用send函数激发协程,挂起,等待服务端处理完成返回后再调用CallBack函数继续下面的流程
Go语言的协程是 语言本身特性 ,erlang和golang都是采用了CSP(Communicating Sequential Processes)模式(Python中的协程是eventloop模型),但是erlang是基于进程的消息通信,go是基于goroutine和channel的通信。
Python和Go都引入了消息调度系统模型,来避免锁的影响和进程/线程开销大的问题。
协程从本质上来说是一种用户态的线程,不需要系统来执行抢占式调度,而是在语言层面实现线程的调度 。因为协程 不再使用共享内存/数据 ,而是使用 通信 来共享内存/锁,因为在一个超级大系统里具有无数的锁,共享变量等等会使得整个系统变得无比的臃肿,而通过消息机制来交流,可以使得每个并发的单元都成为一个独立的个体,拥有自己的变量,单元之间变量并不共享,对于单元的输入输出只有消息。开发者只需要关心在一个并发单元的输入与输出的影响,而不需要再考虑类似于修改共享内存/数据对其它程序的影响。
http.ServeMux
HTTP协议全称超文本传输协议(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和WWW服务器之间通信的规则,通过Internet传送WWW文档的数据传送协议。
Web服务是HTTP协议的一个服务,HTTP协议承载在TCP协议之上。Web服务工作流程
基于HTTP构建的服务标准模型包括客户端和服务端,HTTP请求从客户端发出,服务端接收到请求后进行处理,然后将响应返回给客户端。
HTTP服务端核心工作是如何接收来自客户端的请求,并向客户端返回响应。当HTTP服务器接收到客户端请求时,首先会进入路由模块,路由又称为服务复用器(Multiplexer),路由的工作在于请求找到对应的处理器(Handler),处理器对接收到的请求进行对应处理后,构建响应并返回给客户端。
Go语言通过引入 net/http 包来实现HTTP网络访问,并提供HTTP客户端和服务端的实现。
创建HTTP服务需经过2个阶段
例如:创建HTTP服务
理解HTTP服务关键点在于路由器和处理器
服务复用器
处理器
http.ServeMux 内部使用一个 map 映射来保存所有处理器, http.muxEntry 是一个多路复用器入口实体。
可以发现在 http.muxEntry 字段中存在着 http.Handler 接口类型的 h
虽然 http.ServeMux 也实现了 http.ServerHTTP() 算得上是一个 http.Handler ,但 http.ServeMux 的 http.ServeHTTP() 并非用来处理请求和响应,而是用来查找注册路由对应的处理器。
当 http.ServeMux 路由器设置路由规则后,会通过它实现的 ServeHTTP() 完成请求的分发。当路由器接收到请求后若请求的URI为 * 则会关闭连接,否则会调用自身的 Handler() 来获取对应路由的处理器,最终通过调用 h.ServeHTTP(w,r) 实现对应路由的实现逻辑。
路由器会根据用户请求的URL路径去匹配自身存储的在 map 中的 handler ,最终调用匹配到的 handler 的 ServeHTTP() 以实现执行对应路由的处理函数。
创建 http.ServeMux 实例的方式有两种
http.DefaultServeMux 是默认的 http.ServeMux ,会随着 net/http 包初始化而被自动初始化。
当 http.ListenAndServe() 在没有提供其他处理器的情况下,即它的入参 handler 为 nil 时内部会使用 http.DefaultServeMux 。
net/http 包提供了一组快捷的注册路由的函数 http.Handle() 、 http.HandleFunc() 来配置 http.DefaultServeMux ,快捷函数会将处理器注册到 http.DefaultServeMux 。
二者之间的区别在于 handler 参数上
http.Handle() 的 handler 是一个 http.Handler 接口实例,也就是说传入的 handler 必须要自己提前实现 http.Handler 接口的 ServerHTTP(ResponseWriter, *Request) 方法。
例如:将处理器放入闭包中,将参数传入处理器。
http.HandleFunc() 的 handler 直接是一个原型为 func(ResponseWriter, *Request) 的函数,深入追踪会 HandleFunc() 会发现一个自定义的函数类型。
因此任何具有 func(ResponseWriter, *Request) 签名的函数都能转换成为一个 http.HandlerFunc 类型的对象。同时自定义的函数类型中已经实现了 ServeHTTP() 方法,因此它也是一个 http.Handler 。
例如:返回时使用一个到 http.HandlerFunc 类型的隐式转换
net/http 包提供了 http.NewServeMux() 来创建一个自定义的 http.ServeMux 实例
例如:调用 http.NewServeMux() 会创建服务复用器
例如:创建静态服务
Go中没有继承、多态,可通过接口来实现。而接口则是定义声明的函数签名,任何结构体只要实现与接口函数签名相同的方法,即等同于实现了对应的接口。
例如: http.HandleFunc() 处理函数实现实际上调用默认 http.DefaultServeMux 的 HandleFunc() 方法
例如:调用 http.Handle() 方法则第二个参数 handle 必须实现 http.Handler 接口的 ServeHTTP() 方法,也就是说只要具有 ServeHTTP() 签名方法即可作为处理器。
例如:自定义处理器
http.HandlerFunc 自身已实现 http.Handler 接口的 ServeHTTP() 方法,因此它也是一个处理器。
http.HandlerFunc 的作用是将自定义函数转换为 http.Handler 处理器类型,当调用 http.HandlerFunc(fn) 后会强制将 fn 函数类型转换为 http.HandlerFunc 类型,这样 fn 函数就具有了 ServeHTTP() 方法,同时也就转换成为了一个 http.Handler 处理器。因此 http.HandlerFunc 又称为适配器。
分享名称:go语言多路复用,go 多路复用
文章网址:http://azwzsj.com/article/dsgjjoc.html