- M 从全局 Goroutine 队列加锁获取 G,创建了 G 也要加锁放回去,且资源是全局的(如堆栈内存等),容易造成锁竞争
- M 转移 G 没有把资源利用最大化,比如在 M1 上运行 G1 创建 G2,为了继续运行 G1,需要把 G2 放到别的 M 上,而 G2 和 G1 是相关的,寄存器中保存了 G1 的信息,G2 最好也在 M1 上运行 ,数据局部性差
GMP 优化:
- 每个P 有自己的本地队列,减少锁竞争
- 本地队列平衡,work stealing,减少空转,提高资源利用率
图片指路Golang修养之路。
重要概念:
- 全局队列:存放 G,可以被任意 P 获取,需要加锁
- 本地队列:存放 P 自己的 G,不超过256 个,新建 G’ 时优先加入本地队列;本地队列满了把一半放到全局队列
- P 列表:G 和 M 的调度对象,用来调度 G 和 M 之间的关联关系。所有的 P 在程序启动时创建,数量为 GOMAXPROCS
- M:可理解为内核线程,是对内核线程的封装,必须绑定 P 去获得 G,本地队列为空去全局,全局为空去隔壁。GO 默认最大数量为 10000,当一个 M 被阻塞了会创建新的 M(没有空闲 M 的话),即没有足够的 M 关联 P 并运行其中可运行的G时被创建
- G:协程,用户级的轻量级线程
- P 和 M 数量不能无限
调度器的设计策略:
- 复用线程:即 本地队列空了不选择销毁线程,而是去全局或者隔壁当本线程被阻塞时,会释放 P 去绑定新的 M
- 利用并行:最多有 GOMAXPROCS 个线程分布在多个 CPU 上
- 抢占:不同于 Co-routine 的主动让出,Goroutine 是抢占式,最多占用 10ms
- 当 G 因系统调用(syscall)阻塞时会阻塞M,P 会和 M 解绑,并寻找空闲的 M,没有就新建
- 当 G 因 channel 或 network I/O 阻塞时,不会阻塞 M,M寻找其他可用的G;G恢复后重新进入 P 的队列
特殊角色:
- M0:启动程序后编号为 0 的主线程负责执行初始化和启动第一个G
启动第一个 G 后,M0 就和其他 M 一样了
- G0:每创建一个 M时,创建的第一个 Goroutine仅负责调度 G,不指向任何可执行函数
如在执行完 G1 后切换成 G0,获取 G2 后切换成 G2
每个 M 都有
M0 的 G0 放在全局空间
其他:
- 被唤醒的 M 和 P 组成自旋线程,尝试从全局队列获取 G
n = min(len(GQ)/GOOMAXPROS+1, cap(LQ)/2)
GQ 为全局队列,LQ 为本地队列
-
-
本地没有去全局,全局没有去 netpoll 和事件池,最后去别的 P;此时唤醒一个 M,P继续执行其他的程序,M去寻找
-
hand off 机制
当本线程 M 因为 G 进行的系统调度阻塞时,线程 M 释放 P,P寻找空闲 M
-
监控线程
-
超过两分钟没有 GC,强制执行
-
将长时间未处理的 netpoll 添加到全局队列
-
向长时间运行的 G 发出抢占调度(超过 10ms)
-
-