GO语言GMP模型

G:goroutine协程。

P:processor处理器。起承上启下的作用,对于G来说是处理器,对于M来说用于屏蔽对G的调度,内存控制等逻辑。

M:thread线程。

目录

有关 P 和 M 个数和创建问题

调度器的设计策略

调度器的生命周期

11大场景和参考文献

P调度策略

调度时机



有关 P 和 M 个数和创建问题

P和M都可以自定义设置。go语言启动时,会自动默认10000,但是内核很难支持这么多线程数。

并且P和M并不是一对一的关系,一个M阻塞,P可以创建或者切换到另一个M。

P何时创建:在确定P的最大数量N后,运行时会创建N个P 

M何时创建:没有足够的M来关联P并运行可运行的G的时候(比如所有M都阻塞,但还有很多P就绪任务)就会创建新的M。

调度器的设计策略

为了避免平凡创建和销毁线程,则会对线程进行复用。

1)work stealing 机制

​ 当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程。

2)hand off 机制

​ 当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行。

3)抢占策略

在 Go 中,一个 goroutine 最多占用 CPU 10ms,防止其他 goroutine 被饿死

4)全局G队列

在新的调度器中依然有全局 G 队列,但功能已经被弱化了,当 M 执行 work stealing 从其他 P 偷不到 G 时,它可以从全局 G 队列获取 G。

为了不让全局队列的G饿死,GO语言调度器执行策略:P每执行61次调度,就优先从全局队列取一个G到P中,并执行下一个执行的G。

我们以下图来看看一个go func () 调度流程

1,go func()创建一个goroutine,有两个存储G的队列,一个P的本地队列,一个全家G队列,新创建的G会先保存在P的本地队列中,若P的本地队列已满了则存放全局。

2,若一个P的本地队列为空,则优先从别的MP队列取出G来执行

3,当M调度的某一个G系统调用阻塞或其他阻塞,则runtime将会将这个M和P解除,然后创建一个新的M和这个P组合。

4,当M系统调用结束或者阻塞结束,这个G会尝试取一个空闲的P执行,并放入P的本地队列,若取不到P,则这个M将休眠放入空闲线程中,然后G放入全局队列。

5,G的调度具有亲和性。

调度器的生命周期

 

 其中这里有特殊的M0和G0。

M0:启动程序后的0号主线程,这个 M 对应的实例会在全局变量 runtime.m0 中,不需要在 heap 上分配,M0 负责执行初始化操作和启动第一个 G, 在之后 M0 就和其他的 M 一样了。

G0:每次启动一个M都会创建第一个Goroutine,G0用于调度G,G0不指向任意函数,每一个M都有自己的G0。在调度或系统调用时会使用 G0 的栈空间,全局变量的 G0 是 M0 的 G0。例如:现在本地队列上有G1 和G2,G1 运行完成后 ,M 上运行的 goroutine 切换为 G0,G0 负责调度时协程的切换(函数:schedule)。从 P 的本地队列取 G2,从 G0 切换到 G2,并开始运行 G2 (函数:execute)。实现了线程 M1 的复用。

以下面这个代码为例子:

package main

import "fmt"

func main() {
    fmt.Println("Hello world")
}
  • 1.runtime 创建最初的线程 m0 和 goroutine g0,并把 2 者关联。
  • 2.调度器初始化:初始化 m0、栈、垃圾回收,以及创建和初始化由 GOMAXPROCS 个 P 构成的 P 列表。
  • 3.示例代码中的 main 函数是 main.main,runtime 中也有 1 个 main 函数 ——runtime.main,代码经过编译后,runtime.main 会调用 main.main,程序启动时会为 runtime.main 创建 goroutine,称它为 main goroutine 吧,然后把 main goroutine 加入到 P 的本地队列。
  • 4.启动 m0,m0 已经绑定了 P,会从 P 的本地队列获取 G,获取到 main goroutine。
  • 5.G 拥有栈,M 根据 G 中的栈信息和调度信息设置运行环境
  • 6.M 运行 G
  • 7.G 退出,再次回到 M 获取可运行的 G,这样重复下去,直到 main.main 退出,runtime.main 执行 Defer 和 Panic 处理,或调用 runtime.exit 退出程序。

11大场景和参考文献

GMP 原理与调度-地鼠文档


P调度策略

type p struct{

runq [256]guintptr  //局部队列。用队头队尾指针模拟循环队列

runnext guinptr //下一个执行的G

}

调度策略: runnext -> p.runq -> schet.runq(全局队列) -> 其他P剽窃(随机遍历所有的P,将有G的局部队列剽窃一半到自己队列中) -> 未找到G休眠。因为存在从其他P剽窃行为,所以访问时要加锁。


调度时机

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值