文章持续更新,可以微信搜一搜「小白 debug」第一时间阅读,回复【面试】获免费面试题集。本文已经收录在 GitHub https://github.com/xiaobaiTech/golangFamily , 有大厂面试完整考点和成长路线,欢迎 Star。
GM 模型是什么

在 Go 1.1版本之前,其实用的就是GM模型。
G,协程。通常在代码里用
go关键字执行一个方法,那么就等于起了一个G。M,内核线程,操作系统内核其实看不见
G和P,只知道自己在执行一个线程。G和P都是在用户层上的实现。
除了G和M以外,还有一个全局协程队列,这个全局队列里放的是多个处于可运行状态的G。M如果想要获取G,就需要访问一个全局队列。同时,内核线程M是可以同时存在多个的,因此访问时还需要考虑并发安全问题。因此这个全局队列有一把全局的大锁,每次访问都需要去获取这把大锁。
并发量小的时候还好,当并发量大了,这把大锁,就成为了性能瓶颈。

GMP 模型是什么

基于没有什么是加一个中间层不能解决的思路,golang在原有的GM模型的基础上加入了一个调度器P,可以简单理解为是在G和M中间加了个中间层。
于是就有了现在的GMP模型里。
P的加入,还带来了一个本地协程队列,跟前面提到的全局队列类似,也是用于存放G,想要获取等待运行的G,会优先从本地队列里拿,访问本地队列无需加锁。而全局协程队列依然是存在的,但是功能被弱化,不到万不得已是不会去全局队列里拿G的。GM模型里 M 想要运行G,直接去全局队列里拿就行了;GMP模型里,M想要运行G,就得先获取P,然后从P的本地队列获取G。

新建
G时,新G会优先加入到P的本地队列;如果本地队列满了,则会把本地队列中一半的G移动到全局队列。P的本地队列为空时,就从全局队列里去取。

- 如果全局队列为空时,
M会从其他P的本地队列偷(stealing)一半 G放到自己P的本地队列。

M运行G,G执行之后,M会从P获取下一个G,不断重复下去。

为什么 P 的逻辑不直接加在 M 上
主要还是因为M其实是内核线程,内核只知道自己在跑线程,而golang的运行时(包括调度,垃圾回收等)其实都是用户空间里的逻辑。操作系统内核哪里还知道,也不需要知道用户空间的 golang 应用原来还有那么多花花肠子。这一切逻辑交给应用层自己去做就好,毕竟改内核线程的逻辑也不合适啊。
如果文章对你有帮助,看下文章底部右下角,做点正能量的事情(点两下)支持一下。(疯狂暗示,拜托拜托,这对我真的很重要!)
我是小白,我们下期见。
参考资料
[1]《Golang 调度器 GMP 原理与调度全分析》 ——Aceld :https://learnku.com/articles/41728
[2]《GMP 模型为什么要有 P》 ——煎鱼 :https://mp.weixin.qq.com/s/an7dml9NLOhqOZjEGLdEEw
[3]《深度解密 Go 语言之 Scheduler》 ——qcrao :https://qcrao.com/2019/09/02/dive-into-go-scheduler/#%E4%BB%80%E4%B9%88%E6%98%AF-scheduler
文章推荐:
- i/o timeout,希望你不要踩到这个 net/http 包的坑
- 妙啊! 程序猿的第一本互联网黑话指南
- 程序员防猝死指南
- 我感觉,我可能要拿图灵奖了。。。
- 给大家丢脸了,用了三年 golang,我还是没答对这道内存泄漏题
- 硬核!漫画图解 HTTP 知识点+面试题
- TCP 粘包 数据包:我只是犯了每个数据包都会犯的错 |硬核图解
- 硬核图解!30 张图带你搞懂!路由器,集线器,交换机,网桥,光猫有啥区别?
别说了,关注公众号:【小白 debug】,一起在知识的海洋里呛水吧
关注公众号:【小白 debug】