扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
首先你的理解是错的,不管用户态的API(syscall)是否是同步还是异步,在kernel层面都是异步的。
创新互联建站-专业网站定制、快速模板网站建设、高性价比石家庄网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式石家庄网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖石家庄地区。费用合理售后完善,十多年实体公司更值得信赖。
其实实现原理很简单,就是利用C(嵌入汇编)语言可以直接修改寄存器(setcontext/setjmp/longjmp均是类似原理,修改程序指针eip实现跳转,栈指针实现上线文切换)来实现从func_a调进去,从func_b返回出来这种行为。对于golang来说,func_a/func_b属于不同的goroutine,从而就实现了goroutine的调度切换。
另外对于所有可能阻塞的syscall,golang对其进行了封装,底层实际是epoll方式做的,注册回调后切换到另一个runnable的goroutine。
参考:
Goroutine并发调度模型深度解析手撸一个协程池
Golang 的 goroutine 是如何实现的?
Golang - 调度剖析【第二部分】
OS线程初始栈为2MB。Go语言中,每个goroutine采用动态扩容方式,初始2KB,按需增长,最大1G。此外GC会收缩栈空间。
BTW,增长扩容都是有代价的,需要copy数据到新的stack,所以初始2KB可能有些性能问题。
更多关于stack的内容,可以参见大佬的文章。 聊一聊goroutine stack
用户线程的调度以及生命周期管理都是用户层面,Go语言自己实现的,不借助OS系统调用,减少系统资源消耗。
Go语言采用两级线程模型,即用户线程与内核线程KSE(kernel scheduling entity)是M:N的。最终goroutine还是会交给OS线程执行,但是需要一个中介,提供上下文。这就是G-M-P模型
Go调度器有两个不同的运行队列:
go1.10\src\runtime\runtime2.go
Go调度器根据事件进行上下文切换。
调度的目的就是防止M堵塞,空闲,系统进程切换。
详见 Golang - 调度剖析【第二部分】
Linux可以通过epoll实现网络调用,统称网络轮询器N(Net Poller)。
文件IO操作
上面都是防止M堵塞,任务窃取是防止M空闲
每个M都有一个特殊的G,g0。用于执行调度,gc,栈管理等任务,所以g0的栈称为调度栈。g0的栈不会自动增长,不会被gc,来自os线程的栈。
go1.10\src\runtime\proc.go
G没办法自己运行,必须通过M运行
M通过通过调度,执行G
从M挂载P的runq中找到G,执行G
NIO(非阻塞IO)是一种IO编程模型,Golang中的IO底层实现方式和java NIO模型一致,通俗点说就是都采用了EPOLL。 你在使用golang读文件的时候,goroutine 会默默的挂起,只是你不知道,当读完毕了,goroutine 再次恢复,但你不用担心,goroutine 的挂起和恢复没有java线程那样可怕,你可以认为goroutine 的挂起和恢复就是保存和恢复几个变量的值,其实也是这样的。
剩下的就是goroutine 和 java线程的区别了,goroutine是用户态的线程切换,java采用的是系统线程切换,用汇编语言描述是一个(java)调用int 80软中断,一个没有。 意味着goroutine更轻量级,可以同时相应成千上万的线程切换,java你创造上千个线程就有些吃力了。
因为java线程不能创造过多的线程,如果同时处理上万上千的请求时候,就要考虑在几十个线程来处理上万上千的请求,这就出现了很多请求和线程不可能一一对应,所以通常做法是每个线程分别处理单个请求各个阶段。好比流水线,请求是要加工的商品,每个线程处理一道工序,这样做的好处是每人都做自己熟悉的,对于程序来说每个线程执行的代码永远都是自己很短的一块,这样根据局部优化原理,更具备CPU,内存亲和力,利于JIT。说这样多,就是说如果线程和请求不能一一对应,流水线式的并发编程很麻烦,阅读性也很差,通常是线程A里面一段逻辑代码,线程B又有另一处处理的逻辑代码。
由于goroutine 的轻便,你可以将请求和goroutine 一一对应起来,不用考虑将请求在线程之间换来换去,只关心你的业务逻辑,这就是goroutine 的好处。
总结:
golang的goroutine让你比java更容易编写并发程序,但性能不会有差别(目前来说,golang性能还不能和java比,看过代码就知道了,GC弱到爆),代码不会减少,该写的逻辑还得写。ps,其实golang的(sched)go程切换代码虽然原理和java的fork-join框架一样,但是fork-join比golang的sched代码牛逼不少,开始膜拜Doug Lea吧,golang还有很长的路要走。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流