0
点赞
收藏
分享

微信扫一扫

【简洁版】Go语言GPM模型梳理

少_游 2022-02-06 阅读 43

0. 前提知识

对比分析进程、线程与协程 (htmonster.xyz)

a.协程的M:N关系

  • N:1关系(一个内核线程thread 管理着多个用户协程co-routine
    • 优点:切换开销
    • 缺点:一个挂了就全挂了,一锅端
  • 1:1 关系 (一个内核线程thread 管理着一个用户协程co-routine)
    • 缺点:创建、删除、切换开销

b.Golang中的协程Goroutine

Goroutine其实就是Go语言中的协程。但是不同的是,Golang在语言层次,从runtime系统调用等方面对协程进行了封装。

1. 相关定义

  • G (Goroutine) 协程: 发布出去的任务,由go关键字创建 【携带任务】
  • **P **(Processor) 处理器:调度G到M上,包含包含了gorountine的资源以及可运行的G的队列【分配任务】
  • M (Machine thread) 内核线程:G要放在M上才能运行【寻找执行任务】

2. GMP模型

两个队列

  • 全局队列(Global Queue):存放等待运行的G
  • **P的本地队列:**也是存放等待运行的G,但是数量有限(256)。

新创建的G’, 优先加入P的本地队列。若队列满了,会将本地队列中的一半移入到全局队列。

两个调度器

  • Goroutine调度器:将G分配给M
  • OS调度器: 将M分配到CPU上

两个数量

  • P列表:程序启动时候创建,由a. 环境变量$GOMAXPROCS或者是由b.runtime的方法GOMAXPROCS()决定
  • M列表: 程序运行时没有空闲M时候创建。由a. go程序启动时最大数量默认10000 b. runtime/debug中的SetMaxThreads函数设置 c. M阻塞了,会创建新的M决定。

3. Goroutine调度

2.1设计策略

a. 复用线程

  1. work stealing机制: 线程中没有G时候,从别的绑定P中偷取G
  2. hand off机制: 本线程阻塞时候,释放P给别的线程

b. 并行利用

最多有GOMAXPROCS个线程分布在多个CPU上同时运行

c. 抢占

在Go中,一个goroutine最多占用CPU 10ms

d. 全局队列

当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G。

2.2 go func() 调度过程

  1. go func() 创建一个G
  2. G加入到队列中
    1. 先尝试加入局部队列
    2. 局部队列满了会尝试加入全局队列
  3. P从队列中获取GM与G是1:1的关系
    1. M会从本地队列中取出一个可执行的G
    2. 入股本地队列为空,会从其它的MP组合中偷取一个可执行的G
  4. M循环调度G
  5. M执行G.func()
    1. G如果发生了syscall或则其余阻塞操作,M会阻塞,runtime把线程M从P中摘除(detach)
    2. 创建一个新M或者复用一个休眠M
    3. M来服务于这个P
  6. 销毁G
  7. 返回

2.3 go func 调度流程

  • M0: 启动后的主线,M0负责执行初始化操作启动第一个G, 在之后M0就和其他的M一样了。
  • G0:每次启动一个M都会第一个创建的gourtine,G0仅用于负责调度的G,G0不指向任何可执行的函数, 每个M都会有一个自己的G0

4. Go调度器执行场景

4.1 G1创建G2

4.2 G1执行完毕

4.3 G2开辟过多的G

4.4 P1本地队列负载均衡

4.5 G2本地队列未满加入G8

4.6 唤醒休眠M

4.7 M2尝试批量获取G

4.8 M2偷取P1本地队列中的G

4.9 关闭多余的M

4.10 G阻塞时候P切换M绑定

4.11 G非阻塞时候P-M切换绑定

举报

相关推荐

0 条评论