GMP中为什么要有P?

上图是Go 1.2前的调度器实现。 

GM 调度模型的问题:

  • 单一全局互斥锁(Sched.Lock)和集中状态存储     导致所有 goroutine 相关操作,比如:创建、结束、重新调度等都要上锁。
  • Goroutine 传递问题      M 经常在 M 之间传递”可运行”的 goroutine,这导致调度延迟增大以及额外的性能损耗(刚创建的 G 放到了全局队列,而不是本地 M 执行,不必要的开销和延迟)。
  • Per-M 持有内存缓存 (M.mcache)     每个 M 持有 mcache 和 stack alloc,然而只有在 M 运行 Go 代码时才需要使用的内存(每个 mcache 可以高达2mb),当 M 在处于 syscall 时并不需要。运行 Go 代码和阻塞在 syscall 的 M 的比例高达1:100,造成了很大的浪费。同时内存亲缘性也较差。G 当前在 M运 行后对 M 的内存进行了预热,因为现在 G 调度到同一个 M 的概率不高,数据局部性不好。
  • 严重的线程阻塞/解锁     在系统调用的情况下,工作线程经常被阻塞和取消阻塞,这增加了很多开销。比如 M 找不到G,此时 M 就会进入频繁阻塞/唤醒来进行检查的逻辑,以便及时发现新的 G 来执行。

什么是P?

 “Processor”是一个抽象的概念,并不是真正的物理 CPU。     Dmitry Vyukov的方案是引入一个结构 P,它代表了 M 所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接 M 和 G 的调度上下文,将等待执行的 G 与 M 对接。当 P 有任务时需要创建或者唤醒一个 M 来执行它队列里的任务。所以 P/M 需要进行绑定,构成一个执行单元。P 决定了并行任务的数量,可通过 runtime.GOMAXPROCS 来设定。在 Go1.5 之后GOMAXPROCS 被默认设置可用的核数,而之前则默认为1。

要注意的是在docker中,默认会读取宿主机的核心数,对此我们需要做额外处理。uber做了处理方案,我们引入这个包即可。

import _ "go.uber.org/automaxprocs"

引入了 local queue,因为 P 的存在,runtime 并不需要做一个集中式的 goroutine 调度,每一个 M 都会在 P's local queue、global queue 或者其他 P 队列中找 G 执行,减少全局锁对性能的影响。 这也是 GMP Work-stealing 调度算法的核心。注意 P 的本地 G 队列还是可能面临一个并发访问的场景,为了避免加锁,这里 P 的本地队列是一个 LockFree的队列,窃取 G 时使用 CAS 原子操作来完成。关于LockFree 和 CAS 的知识参见 Lock-Free。

“go func()” 经历了什么过程 

在这里插入图片描述

  • 有两个存储G的队列,⼀个是局部调度器P的本地队列、⼀个是全局G队列。新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列中
  • G只能运⾏在M中,⼀个M必须持有⼀个P,M与P是1:1的关系。M会从P的本地队列弹出⼀个可执⾏状态的G来执⾏,如果P的本地队列为空,就会想其他的MP组合偷取⼀个可执⾏的G来执⾏。当M执⾏work stealing从其他P偷不到G时,它可以从全局G队列获取G
  • 当M执⾏某⼀个G时候如果发⽣了syscall或则其余阻塞操作,M会阻塞,如果当前有⼀些G在执⾏,runtime会把这个线程M从P中摘除(detach),然后再创建⼀个新的操作系统的线程(如果有空闲的线程可⽤就复⽤空闲线程)来服务于这个P
  • 当M系统调⽤结束时候,这个G会尝试获取⼀个空闲的P执⾏,并放⼊到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加⼊到空闲线程中,然后这个G会被放⼊全局队列中

 

调度器的生命周期


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

G0
G0是每次启动⼀个M都会第⼀个创建的gourtine,G0仅⽤于负责调度的G,G0不指向任何可执⾏的函数,
每个M都会有⼀个⾃⼰的G0。在调度或系统调⽤时会使⽤G0的栈空间, 全局变量的G0是M0的G0。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值