文章目录

  • 前言
  • 1. Goroutine调度器的基本概念
  • 2. GMP 数据结构
    • 2.1 G
    • 2.2 M
    • 2.3 P
  • 3. M缓冲池
  • 4. 调度策略
    • 4.1 work stealing机制
    • 4.2 hand off 机制
    • 4.3 抢占
    • 4.4 阻塞的两种情况
    • 4.5 拓展
  • 5. go func()调度流程
  • 6. P和M的个数
    • 6.1 P的数量
    • 6.2 M的数量
    • 6.3 P和M什么时候会被创建
    • 6.4 问题?

前言

线程数过多,意味着操作系统会不断地切换线程,频繁的上下文切换就成了性能瓶颈。
     Golang的调度模型是GMP模型,它提供一种机制,可以在线程中自己实现调度,上下文切换更轻量,从而达到了线程数少,而并发数并不少的效果。而线程中调度的就是Goroutine.

调度的机制用一句话描述就是:runtime准备好G,M,P,然后M绑定P,M从本地或者是全局队列中获取G,然后切换到G的执行栈上执行G上的任务函数,调用goexit做清理工作并回到M,如此反复

接下来我来分模块介绍一下Golang的GMP模型及创建流程

1. Goroutine调度器的基本概念

G(goroutine)

  • 即Go协程,每个go关键字都会创建一个协程,它存储了goroutine的执行stack信息(运行时栈信息)、goroutine状态以及goroutine的任务函数等
  • 在G眼中只有P,P就是运行G的 CPU

M(machine)

  • 工作线程,在Go中称为Machine。
  • M是真正调度系统的执行者,它会优先从关联的 P 的本地队列中直接获取中可运行的G,如果本地队列没有的话, 再到调度器持有的全局队列中领取一些任务或是向其他的MP组合偷一半可以执行的G来执行,M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。

P(processor)

  • processor处理器,它包含了运行 goroutine 的资源,
  • 它用于处理M与G的关系:如果线程想运行 goroutine,必须先获取 P,P 中还包含了可运行的 G 队列
  • P的个数在程序启动时决定,默认等同与CPU的核数,通过 runtime.GOMAXPROCS() 设置P的个数

M必须拥有P才可以执行G中的代码,P含有一个包含多个G的队列,P可以调度G交由M执行

2. GMP 数据结构

g、m、p数据结构均在 runtime/runtime2.go

2.1 G

g 的关键字段

type g struct {stack       stack // 当前G的栈范围stackguard0 uintptr // 判读当前G是否被抢占preempt       bool // 抢占信号preemptStop   bool // 抢占时将状态修改成 `_Gpreempted`preemptShrink bool // 在同步安全点收缩栈_panic       *_panic // 最内侧的 panic 结构体_defer       *_defer // 最内侧的延迟函数结构体m              *m // 当前G占用的线程sched          gobuf // 调度相关数据的存储atomicstatus   uint32 // G的状态
}

2.2 M

m的关键字段

  • p最多可以创建10000个线程
  • 最多只有GOMAXPROCS个活跃线程(与核数一致),这样不会频繁地切换线程上下文
type m struct {g0   *g         // 调度栈   使用的Gcurg *g         // 当前在M上运行的Gp      puintptr // 正在运行代码的Pnextp  puintptr // 暂存的Poldp   puintptr // 之前使用的P
}

2.3 P

p的关键字段

type p struct {m           muintptr // 调度的Mrunqhead uint32 // G队列头runqtail uint32 // G队列尾runq     [256]guintptr // G队列runnext guintptr // 下一个可运行的Gstatus int // 当前P的状态
}

状态取值:

  • _Pidle:运行队列为空,没有需要运行的G
  • _Prunning:M正在执行用户G
  • _Psyscall:M处于系统调用
  • _Pgcstop:M处于GC垃圾回收的stop中
  • _Pdead:P不再被使用

3. M缓冲池

在介绍GMP概念的时候说到:P的个数默认等于CPU核数,每个M必须持有一个P才可以执行G,一般情况下M的个数会略大于P的个数,这多出来的M将会在G产生系统调用时发挥作用。类似线程池,Go也提供一个M的池子,需要时从池子中获取,用完放回池子,不够用时就再创建一个。

4. 调度策略

复用线程:避免频繁的创建、销毁线程,而是对线程的复用。

4.1 work stealing机制

当M没有可运行的 G 时,尝试从其他线程M绑定的 P 偷取一半的G过来,而不是销毁线程。

work stealing机制触发:当前M线程的P本地队列中没有可运行的G时 并且 全局队列G中也没有可运行的G时,则会执行workstealing机制.

即:本地队列→\rightarrow→全局队列→\rightarrow→窃取

4.2 hand off 机制

当M阻塞时,M释放绑定的 P(MP分离),把 P 转移给其他空闲的线程执行。

4.3 抢占

在 coroutine 中要等待一个协程主动让出 CPU 才执行下一个协程,在 Go 中,一个 goroutine 执行的时间不能超过 10ms,防止其他 goroutine 被饿死。

4.4 阻塞的两种情况

用户态阻塞/唤醒
例如网络IO、阻塞式channel、sleep等场景(简单来说就是CPU这时候对于这个协程没有事情要做),对于这类阻塞会将G暂时挂起到某一临时等待队列中,待阻塞结束后重新寻找P放入。

系统调用阻塞
M 执行某一个 G 时,如果发生系统调用或则其余阻塞操作,M 会阻塞,如果当前有 G 在执行,runtime 会将这个 MP 进行分离,如果有空闲的M就用或者是从线程池中取,如果没有就创建一个新的M 来服务于这个 P;

当 M 系统调用结束时候,这个 G 会尝试获取一个空闲的 P 执行,并放入到这个 P 的本地队列。如果获取不到 P,那么这个线程 M 变成休眠状态, 加入到空闲线程中,然后这个 G 会被放入全局队列中

4.5 拓展

判定阻塞的原理:

go程序启动时会首先创建一个特殊的内核线程 sysmon,用来监控和管理,其内部是一个循环:

记录所有 P 的 G 任务的计数 schedtick,schedtick会在每执行一个G任务后递增

如果检查到 schedtick 一直没有递增,说明这个 P 一直在执行同一个 G 任务,如果超过10ms,就在这个G任务的栈信息里面加一个 tag 标记

然后这个 G 任务在执行的时候,如果遇到非内联函数调用,就会检查一次这个标记,然后中断自己,把自己加到队列末尾,执行下一个G

如果没有遇到非内联函数 调用的话,那就会一直执行这个G任务,直到它自己结束;如果是个死循环,并且 GOMAXPROCS=1 的话。那么一直只会只有一个 P 与一个 M,且队列中的其他 G 不会被执行!

5. go func()调度流程

下方图片转自Go夜读 go

  1. 使用go关键子创建一个G,写法:go func(){}
  2. 将G放入P的本地队列(如果当前M绑定的P的本地队列满了,会放在全局队列中)
  3. 唤醒或者新建M来执行任务
  4. 进入调度循环(M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去;)
  5. 尽力获取可执行的G,并执行(如果p本地队列没有可运行的G时,会去全局队列中拿取一半的,如果全局队列中也没有,则会进行执行work stealing机制,会随机的去另一个线程M中的P本地队列偷取一半的G来运行)
  6. 清理现场并重新进入调度循环

6. P和M的个数

6.1 P的数量

P的数量会由启动时环境变量$GOMAXPROCS 或是runtime的方法 GOMAXPROCS()来设定

6.2 M的数量

  • go程序启动时默认的M的最大数量为 10000
  • runtime/debug 中的 SetMaxThreads 函数,设置 M 的最大数量
  • 当某个M阻塞了,会创建新得的M

6.3 P和M什么时候会被创建

  1. P:在确定了P的最大数量为n的时候,运行时系统会根据这个n创建n个P
  2. M:当的M都阻塞了,但是绑定的P中还有很多就绪任务G,这时会去寻找空闲的M或者去线程池中找,且找不到空闲的M的情况下会创建新的M

6.4 问题?

问题来自B战评论

整体的逻辑与单线程调度器没有太多区别,因为我们的程序中可能同时存在多个活跃线程,所以多线程调度器引入了 GOMAXPROCS 变量帮助我们灵活控制程序中的最大处理器数,即活跃线程数。

这个GOMAXPROCS 到底是 P的个数,还是M的个数呢?

答:GOMAXPROCS是限制P的个数,你可以理解成M是线程,P是M需要执行G的时候需要持有的局部资源,只有M持有P的时候才有局部资源可以执行G。注意,也存在M持有G但是不持有P的情况,这时候一般是由于M持有P执行G的时候陷入了长时间的系统调用,被系统监控sysmon发现后将P夺走,将P给另一个M用来继续执行其他G,被夺走P的M此时陷入系统调用,不使用CPU了,也不执行G。因此,总体上可以这么认为,M如果需要访问CPU资源,那么就需要持有P,同时有多少个CPU核心,那么就有多少个P,同时也就有多少个M可以使用CPU。

如果想要了解更多请看文档或源码:
G-P-M 模型的设计者的文档

Golang GMP调度模型详解相关推荐

  1. golang——GMP调度模型详解

    目录 一.Golang调度器由来 存在问题: 3种协程和线程的关系 二.Golang对协程的处理 协程和goroutine关系 Go的GMP调度模型 P 和 M 何时会被创建 P和M的个数 调度器的设 ...

  2. Go面试必问——GMP调度模型详解

    来源:http://www.topgoer.com/并发编程/GMP原理与调度.html 文章目录 GMP 原理与调度 Golang "调度器" 的由来? (1) 单进程时代不需要 ...

  3. Golang GMP调度模型

    解释GMP模型含义 M结构是Machine,系统线程,它由操作系统管理,goroutine就是跑在M之上的:M是一个很大的结构,里面维护小对象内存cache(mcache).当前执行的goroutin ...

  4. golang之gmp调度模型

    原始调度模型 我们把线程分为内核级线程和用户态线程,内核级的线程在切换线程时,开销比较大,需要系统调用,但是,用户态线程不是这样,用户态的线程之间的切换不需要系统调用,从而把切换的开销比较小. gol ...

  5. Golang知识点二、GMP调度模型

    GMP调度模型 1. 调度器由来   调度器分为进程调度器和线程调度器. 1.1. 单进程时代   单进程系统存在一定问题:1. 单一执行流程.计算机只能一个任务一个任务处理 2. 进程阻塞所带来的C ...

  6. golang程序启动流程详解

    golang程序启动流程详解 环境 go1.16.5 linux/amd64 用例 package mainimport "fmt"func main() {fmt.Println ...

  7. Go 语言编程 — 并发 — GMP 调度模型

    目录 文章目录 目录 并发和并行 如何交互?CSP 通信模型 如何调度?GMP 调度模型 用户级线程模型(多对一) 内核级线程模型(一对一) 两级线程模型(多对多) GMP 线程模型 Go Runti ...

  8. 使用pickle保存机器学习模型详解及实战(pickle、joblib)

    使用pickle保存机器学习模型详解及实战 pickle模块实现了用于序列化和反序列化Python对象结构的二进制协议. "Pickling"是将Python对象层次结构转换为字节 ...

  9. Transformer 模型详解

    Transformer 是 Google 的团队在 2017 年提出的一种 NLP 经典模型,现在比较火热的 Bert 也是基于 Transformer.Transformer 模型使用了 Self- ...

最新文章

  1. 关于开始申请2010年4月份微软MVP的通知!
  2. C#的多线程机制探索4
  3. 在Windows IoT上使用网络摄像头
  4. 第2章:Maven的安装/2.2 Linux下的安装
  5. 列举在Web前端开发中经常会设置的特殊样式!
  6. LeetCode 451. 根据字符出现频率排序(map+优先队列)
  7. java 中for循环中重复定义的变量 为什么不报错?
  8. Malta中any函数
  9. 关于Oracle RAC调整网卡MTU值的问题
  10. Apache Commons DbUtils 入门
  11. Apple ID 被盗用的 5 个征兆,遇到其中一种,建议赶快改密码
  12. modelsim安装教程
  13. 2022最新软件设计师历年真题和答案解析分享!
  14. 单片机中断程序详解(转)
  15. 解线性方程 matlab,用matlab求线性方程的解
  16. 超声波传感器(CHx01) 学习笔记 Ⅵ - 原始数据
  17. @click.stop作用(阻止点击事件继续传播,即阻止事件冒泡)
  18. 面试-android
  19. SQL数据更新、视图
  20. 【香蕉oi】燃烧的火焰(最短路、状压)

热门文章

  1. PyCharm+PyQt5(5.15.2)+mysql, PyQt5连接mysql,踩坑与解决办法
  2. Python递归生成多叉树结构之treelib
  3. axios同步请求--
  4. 随机变量的分布及其数字特征
  5. java 小程序 公众号_如何玩转小程序+公众号?手把手教你JeeWx小程序CMS与公众号关联...
  6. GD32F103 中文芯片手册,用户手册
  7. python自动化导出数据库表结构到word
  8. 微信小程序 解决自定义顶部导航栏被键盘挤压的问题
  9. java优化上传速度慢怎么办_我是如何让minio client上传速度提高几十倍的
  10. 【MYSQL高级】Mysql的SQL性能分析【借助EXPLAIN分析】