调度器的三个基本对象
Go 的协程(goroutine)
和我们常见的线程(Thread)
一样,拥有其调度器。
- G (
Goroutine
),代表协程,也就是每次代码中使用go 关键词
时候会创建的一个对象。存储了goroutine的执行stack信息、状态、任务函数等,G对象是可以重用的。 - M (
Work Thread
),代表着真正的执行计算资源,工作线程。 - P (
Processor
),表示逻辑processor,P的数量决定了最大可并行的G的数量。代表一个处理器
,又称上下文
M-P-G三者的关系与特点:
- 每一个运行的 M 都必须绑定一个 P,线程M 创建后会去检查并执行G (goroutine)对象
- 每一个 P 保存着一个协程G 的
队列
- 除了每个 P 自身保存的 G 的队列外,调度器还拥有一个全局的 G 队列
- M 从
队列中
提取 G,并执行 - P 的个数就是
GOMAXPROCS
(最大256),启动时固定的,一般不修改 - M 的个数和 P 的个数不一定一样多(会有休眠的M 或 P不绑定M )(最大10000)
- P 是用一个全局数组(255)来保存的,并且维护着一个全局的 P 空闲链表
局部G队列与全局G队列的关系
- 全局G任务队列会和各个本地G任务队列按照一定的策略互相交换。没错,就是
协程任务
交换 - G任务的执行顺序是,先从
本地队列
找,本地没有则从全局队列
找 - 转移
- 局部与全局,全局G个数 / P个数
- 局部与局部,一次性转移一半
Gorutine从入队到执行
-
当我们创建一个G对象,就是
gorutine
,它会加入到本地队列或者全局队列 -
如果还有空闲的P,则创建一个M 绑定该 P ,注意!这里,P 此前必须还没绑定过M 的,否则不满足空闲的条件。细节点:
-
先找到一个空闲的P,如果没有则直接返回
-
P 个数不会占用超过自己设定的cpu个数
-
P 在被 M 绑定后,就会初始化自己的 G 队列,此时是一个
空队列
-
注意这里的
一个点
!- 无论在哪个 M 中创建了一个 G,只要 P 有空闲的,就会引起新 M 的创建
- 不需考虑当前所在 M 中所绑的 P 的 G 队列是否已满
- 新创建的 M 所绑的 P 的初始化队列会从其他 G 队列中取任务过来
-
这里留下第一个问题:
如果一个G任务执行时间太长,它就会一直占用 M 线程,由于队列的G任务是顺序执行的,其它G任务就会阻塞,如何避免该情况发生? --①
-
-
M 会启动一个
底层线程
,循环执行
能找到的 G 任务。这里的寻找的 G 从下面几方面找:- 当前 M 所绑的 P 队列中找
- 去别的 P 的队列中找
- 去全局 G 队列中找
-
G任务的执行顺序是,先从本地队列找,本地没有则从全局队列找
-
程序启动的时候,首先跑的是主线程,然后这个主线程会绑定第一个 P
-
入口 main 函数,其实是作为一个 goroutine 来执行