GM模型和GMP模型理解

在这里插入图片描述

GMP模型:

GMP是GO运行时调度层面的实现。有4个重要结构:G、M、P、Sched。对照上面的图理解

G:表示goroutine ,G的数量理论上无限制,只受内存的影响。创建一个G的初始内存为2-4k,低配计算机也能创建几十万个协程,而且执行完的 G,go语言还是会把他 P 本地/全局队列的闲置列表等待复用。

M:表示内核线程(machine),是对操作系统的一个封装,操作系统感知不到协程的存在,所以为了在cpu上执行代码必须要有这个内核态线程。
    流程: M 是由操作系统创建,再绑定了有效的 P 后,进入一个调度循环获取 G(调度循环是 从 P 的本地/全局队列中获取 G,并执行 G 的函数,最后调用goexit做清理工作再回到 M,如此反复)。M 不会保留 G 的状态,这是 G 可以跨 M 调度的基础。 M 的数量有限制,默认是1w个,可以自行设置(debug.SetMaxThreads()),如果 M 有空闲就会回收或者休眠(休眠是为了后续线程的复用)。

P:表示调度器(processor),处理 M 执行 G 所需的上下文资源,只有将 G 和 M 绑定,才能让 P 的本地队列中的 G 真正运行起来。P 的数量决定了系统中最大可并行的 G 的数量。 P 的数量受计算机的CPU内核数影响(可以通过$GOMAXPROCS或在runtime.GOMAXPROCS()来设置,默认是计算机的cpu内核数,但是最大并不能超过这个数。该拯救者16核)

Sched: 调度器结构,用于维护 M 和 G 的全局队列,以及调度器的一些状态信息。

在这里插入图片描述
在这里插入图片描述

GOMAXPROCS是cpu的核心数,最多创建这么多 P (比如电脑是16核,最多就是16个 P )

在 GPM 模型,有一个全局队列(Global Queue):存放等待运行的 G,还有一个 P 的本地队列:也是存放等待运行的 G,但数量有限,不超过 256 个。GPM 的调度流程从 go func()开始创建一个 goroutine,新建的 goroutine 优先保存在 P 的本地队列中,如果 P 的本地队列已经满了,则会保存到全局队列中。M 会从 P 的队列中取一个可执行状态的 G 来执行,如果 P 的本地队列为空,就会从其他的 MP 组合偷取一个(workStealing机制)可执行的 G 来执行,当 M 执行某一个 G 时候发生系统调用或者阻塞,M 阻塞,如果这个时候 G 在执行,runtime 会把这个线程 M 从 P 中摘除,然后创建一个新的操作系统线程(内核态线程 M)来服务于这个 P,当 M 系统调用结束时,这个 G 会尝试获取一个空闲的 P 来执行,并放入到这个 P 的本地队列,如果这个线程 M 变成休眠状态,加入到空闲线程中,然后整个 G 就会被放入到全局队列中。
关于 G,P,M 的个数问题,G 的个数理论上是无限制的,但是受内存限制,P 的数量一般建议是逻辑 CPU 数量的 2 倍,M 的数据默认启动的时候是 10000,内核很难支持这么多线程数,所以整个限制客户忽略,M 一般不做设置,设置好 P,M 一般都是要大于 P。
在这里插入图片描述

GM模型:

最开始的是GM模型没有 P 的,当然也是M:N的两级线程模型,但是会出现一些问题:

  • 全局队列的锁竞争。M从全局队列中添加或获取 G 的时候,都是需要上锁的(下图执行步骤要加锁),这样就会导致锁竞争,虽然达到了并发安全,但是性能是非常差的。
  • M 转移 G 会有额外开销。M 在执行 G 的时候,假设 M1 执行的 G1 创建了 G2,新创建的就要放到全局队列中去,但是这时有一个空闲的 M2 获取到了 G2,那么这样 G1、G2 会被不同的 M 执行,但是 M1 中本来就有 G2 的信息,M2 在 G1 上执行是更好的,而且取和放到全局队列也会来回加锁,这样都会有一部分开销。
  • 线程的使用效率不能最大化。M 拿不到的时候就会一直空闲,阻塞的时候也不会切换。
    也就是没有 workStealing 机制和 handOff 机制

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值