GM图
在 Go 1.1
版本之前,其实用的就是GM
模型。
go
关键字执行一个方法,那么就等于起了一个G
。G
和P
,只知道自己在执行一个线程。G
和P
都是在用户层上的实现。除了G
和M
以外,还有一个全局协程队列,这个全局队列里放的是多个处于可运行状态的G
。M
如果想要获取G
,就需要访问一个全局队列。同时,内核线程M
是可以同时存在多个的,因此访问时还需要考虑并发安全问题。因此这个全局队列有一把全局的大锁,每次访问都需要去获取这把大锁。
并发量小的时候还好,当并发量大了,这把大锁,就成为了性能瓶颈。
GM模型
GMP图
基于没有什么是加一个中间层不能解决的思路,golang
在原有的GM
模型的基础上加入了一个调度器P
,可以简单理解为是在G
和M
中间加了个中间层。
于是就有了现在的GMP
模型里。
P
的加入,还带来了一个本地协程队列,跟前面提到的全局队列类似,也是用于存放G
,想要获取等待运行的G
,会优先从本地队列里拿,访问本地队列无需加锁。而全局协程队列依然是存在的,但是功能被弱化,不到万不得已是不会去全局队列里拿G
的。GM
模型里M想要运行G
,直接去全局队列里拿就行了;GMP
模型里,M
想要运行G
,就得先获取P
,然后从 P
的本地队列获取 G
。GMP模型
G
时,新G
会优先加入到 P
的本地队列;如果本地队列满了,则会把本地队列中一半的 G
移动到全局队列。P
的本地队列为空时,就从全局队列里去取。GMP模型-获取全局协程队列
M
会从其他 P
的本地队列偷(stealing)一半G放到自己 P
的本地队列。GMP模型-stealing
M
运行 G
,G
执行之后,M
会从 P
获取下一个 G
,不断重复下去。GMP模型-循环执行
主要还是因为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
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8