网站建设与管理 pdf,手机视频制作软件最火,做网站的的价位,wordpress 国家列表一、GMP 调度器
1、调度器理解思路
理解golang的调度器要从进程到协程演进来说明#xff1a; 进程---线程---协程--- golang的协程#xff08;goroutine#xff09; 从上图可以看出#xff0c;进程到多线程到协程#xff0c;最终目的就是为了提高CPU的利用率…一、GMP 调度器
1、调度器理解思路
理解golang的调度器要从进程到协程演进来说明 进程---线程---协程--- golang的协程goroutine 从上图可以看出进程到多线程到协程最终目的就是为了提高CPU的利用率但都存在着不同的问题因此golang的调度器也是为解决协程引发的问题提高PCU利用率从而提高Go语言效率
2.GMP概念 G — 表示 Goroutine它是一个待执行的任务 M — 表示操作系统的线程它由操作系统的调度器调度和管理 P — 表示处理器它可以被看做运行在线程上的本地调度器 首先Go语言合理利用多核的进行并行处理也就是多线程 对应GMP中的M。 为了减少内核级线程调用切换成本Go基于协程进行程序任务执行单元。对应GMP中的G。 同时go实现对G的调度器对应GMP中的P。
3.GMP模型的简介
面对之前调度器的问题Go 设计了新的调度器。
在新调度器中除了 M (thread) 和 G (goroutine)又引进了 P (Processor)。
Processor它包含了运行 goroutine 的资源如果线程想运行 goroutine必须先获取 PP 中还包含了可运行的 G 队列。 在 Go 中线程是运行 goroutine 的实体调度器的功能是把可运行的 goroutine 分配到工作线程上。 Goroutine 调度器和 OS 调度器是通过 M 结合起来的每个 M 都代表了 1 个内核线程OS 调度器负责把内核线程分配到 CPU 的核上执行。 4.调度器设计策略 5.go func() 调度流程 6.调度器生命周期 特殊的 M0 和 G0
M0M0 是启动程序后的编号为 0 的主线程这个 M 对应的实例会在全局变量 runtime.m0 中不需要在 heap 上分配M0 负责执行初始化操作和启动第一个 G 在之后 M0 就和其他的 M 一样了。
G0G0 是每次启动一个 M 都会第一个创建的 goroutineG0 仅用于负责调度的 GG0 不指向任何可执行的函数每个 M 都会有一个自己的 G0。在调度或系统调用时会使用 G0 的栈空间全局变量的 G0 是 M0 的 G0。
7.Go 调度器调度场景过程全解析
(1) 场景 1 P 拥有 G1M1 获取 P 后开始运行 G1G1 使用 go func() 创建了 G2为了局部性 G2 优先加入到 P1 的本地队列。 (2) 场景 2 G1 运行完成后 (函数goexit)M 上运行的 goroutine 切换为 G0G0 负责调度时协程的切换函数schedule。从 P 的本地队列取 G2从 G0 切换到 G2并开始运行 G2 (函数execute)。实现了线程 M1 的复用。 (3) 场景 3 假设每个 P 的本地队列只能存 3 个 G。G2 要创建了 6 个 G前 3 个 GG3, G4, G5已经加入 p1 的本地队列p1 本地队列满了。 (4) 场景 4 G2 在创建 G7 的时候发现 P1 的本地队列已满需要执行负载均衡 (把 P1 中本地队列中前一半的 G还有新创建 G 转移到全局队列)
实现中并不一定是新的 G如果 G 是 G2 之后就执行的会被保存在本地队列利用某个老的 G 替换新 G 加入全局队列 这些 G 被转移到全局队列时会被打乱顺序。所以 G3,G4,G7 被转移到全局队列。
(5) 场景 5 G2 创建 G8 时P1 的本地队列未满所以 G8 会被加入到 P1 的本地队列。 G8 加入到 P1 点本地队列的原因还是因为 P1 此时在与 M1 绑定而 G2 此时是 M1 在执行。所以 G2 创建的新的 G 会优先放置到自己的 M 绑定的 P 上。
(6) 场景 6 规定在创建 G 时运行的 G 会尝试唤醒其他空闲的 P 和 M 组合去执行。 假定 G2 唤醒了 M2M2 绑定了 P2并运行 G0但 P2 本地队列没有 GM2 此时为自旋线程没有 G 但为运行状态的线程不断寻找 G。
(7) 场景 7 M2 尝试从全局队列 (简称 “GQ”) 取一批 G 放到 P2 的本地队列函数findrunnable()。M2 从全局队列取的 G 数量符合下面的公式
n min(len(GQ)/GOMAXPROCS 1, len(GQ/2)) 至少从全局队列取 1 个 g但每次不要从全局队列移动太多的 g 到 p 本地队列给其他 p 留点。这是从全局队列到 P 本地队列的负载均衡。 假定我们场景中一共有 4 个 PGOMAXPROCS 设置为 4那么我们允许最多就能用 4 个 P 来供 M 使用。所以 M2 只从能从全局队列取 1 个 G即 G3移动 P2 本地队列然后完成从 G0 到 G3 的切换运行 G3。
(8) 场景 8 假设 G2 一直在 M1 上运行经过 2 轮后M2 已经把 G7、G4 从全局队列获取到了 P2 的本地队列并完成运行全局队列和 P2 的本地队列都空了如场景 8 图的左半部分。 全局队列已经没有 G那 m 就要执行 work stealing (偷取)从其他有 G 的 P 哪里偷取一半 G 过来放到自己的 P 本地队列。P2 从 P1 的本地队列尾部取一半的 G本例中一半则只有 1 个 G8放到 P2 的本地队列并执行。
(9) 场景 9 G1 本地队列 G5、G6 已经被其他 M 偷走并运行完成当前 M1 和 M2 分别在运行 G2 和 G8M3 和 M4 没有 goroutine 可以运行M3 和 M4 处于自旋状态它们不断寻找 goroutine。 为什么要让 m3 和 m4 自旋自旋本质是在运行线程在运行却没有执行 G就变成了浪费 CPU. 为什么不销毁现场来节约 CPU 资源。因为创建和销毁 CPU 也会浪费时间我们希望当有新 goroutine 创建时立刻能有 M 运行它如果销毁再新建就增加了时延降低了效率。当然也考虑了过多的自旋线程是浪费 CPU所以系统中最多有 GOMAXPROCS 个自旋的线程 (当前例子中的 GOMAXPROCS4所以一共 4 个 P)多余的没事做线程会让他们休眠。
(10) 场景 10 假定当前除了 M3 和 M4 为自旋线程还有 M5 和 M6 为空闲的线程 (没有得到 P 的绑定注意我们这里最多就只能够存在 4 个 P所以 P 的数量应该永远是 MP, 大部分都是 M 在抢占需要运行的 P)G8 创建了 G9G8 进行了阻塞的系统调用M2 和 P2 立即解绑P2 会执行以下判断如果 P2 本地队列有 G、全局队列有 G 或有空闲的 MP2 都会立马唤醒 1 个 M 和它绑定否则 P2 则会加入到空闲 P 列表等待 M 来获取可用的 p。本场景中P2 本地队列有 G9可以和其他空闲的线程 M5 绑定。 (11) 场景 11 G8 创建了 G9假如 G8 进行了非阻塞系统调用。 M2 和 P2 会解绑但 M2 会记住 P2然后 G8 和 M2 进入系统调用状态。当 G8 和 M2 退出系统调用时会尝试获取 P2如果无法获取则获取空闲的 P如果依然没有G8 会被记为可运行状态并加入到全局队列M2 因为没有 P 的绑定而变成休眠状态 (长时间休眠等待 GC 回收销毁)。 参考https://learnku.com/articles/41728