GMP 线程调度模型

GMP 线程调度模型是 Go 协程调度的 CSP 并发模型实现,是对两级线程模型进行了一定程度的改进,使它能够更加灵活地进行线程之间的调度。

Golang 内部有三个对象,goroutine,machine,processor。

研究这块最好结合源码。

GMP 调度模型

G=Goroutine 协程,P=Processor 处理器, M=Thread 线程

  • 全局队列(Global Queue):存放等待运行的 G。
  • P 的本地队列:同全局队列类似,存放的也是等待运行的 G,存的数量有限,不超过 256 个。新建 G’时,G’优先加入到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列。
  • P:所有的 P 都在程序启动时创建,并保存在数组中,最多有 GOMAXPROCS(可配置) 个。
  • M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。

说明:

  • G,Goroutine,协程,轻量级的用户线程,是对 Go 语言中代码片段的封装;
  • M,Machine,物理线程(工作线程),对内核级线程的封装,一个 Machine 对应一个内核线程,当前操作系统分配到当前 Go 程序的线程数,由于 Go 语言本身的限制,Go 程序启动时会设置 M 的最大数量默认为10000,但是内核很难支持这么多的线程数,所以这个限制可以忽略。可以通过 runtime/debug 中的 SetMaxThreads 函数来设置 M 的最大数量;
  • P,Processor,处理器(上下文),G和M的调度对象,用来调度G和M之间的关联关系,程序启动时创建,其数量可通过 GOMAXPROCS() 来设置,默认为 CPU 核心数,这意味着在程序执行的任意时刻都只有 GOMAXPROCS 个 goroutine 在同时运行。

Goroutine 调度器和 OS 调度器是通过 M 结合起来的,每个 M 都会与 1 个内核线程绑定,OS 调度器负责把内核线程分配到 CPU 的核上执行。在运行时一个 M 同时只能绑定1个 P,M 和 P 是一对一绑定的,M 和P 的组合共同构成了 G 的有效运行环境。但 M 和 P 会适时的组合和断开,以保证待执行 G 队列能够得到及时执行。而 P 和 G 的关系是一对多的,多个可执行 G 将会顺序排成一个队列挂在某个 P 上面。在运行过程中,M 和内核线程之间的对应关系不会变,在 M 的生命周期内,它只会和一个内核线程绑定,而 M 和 P 以及 P 和 G 之间的关系都是动态可变的。

M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是1,也有可能会创建很多个 M 出来。但由于 P 的存在,G 和 M 可以呈现出多对多的关系。当一个正在与某个 M 对接并运行着的 G,需要因某个事件(比如等待 I/O 或锁的解除)而暂停运行的时候,调度器总会及时地发现,并把这个 G 与那个 M 分离开,以释放计算资源供那些等待运行的 G 使用。 

P和M何时会被创建

  • P何时创建:在确定了 P 的最大数量 n 后,运行时系统会根据这个数量创建 n 个 P。
  • M何时创建:没有足够的 M 来关联 P 并运行其中的可运行的 G。比如所有的 M 此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的 M,而没有空闲的,就会去创建新的 M。

 go func() 调度流程

  1. 我们通过 go func () 来创建一个 goroutine;
  2. 有两个存储 G 的队列,一个是局部调度器 P 的本地队列、一个是全局 G 队列。新创建的 G 会先保存在 P 的本地队列中,如果 P 的本地队列已经满了就会保存在全局的队列中;
    ​3. G 只能运行在 M 中,一个 M 必须持有一个 P,M 与 P 是 1:1 的关系。M 会从 P 的本地队列弹出一个可执行状态的 G 来执行,如果 P 的本地队列为空,就会想其他的 MP 组合偷取一个可执行的 G 来执行;
    ​4. 一个 M 调度 G 执行的过程是一个循环机制;
  3. 当 M 执行某一个 G 时候如果发生了 syscall 或则其余阻塞操作,M 会阻塞,如果当前有一些 G 在执行,runtime 会把这个线程 M 从 P 中摘除 (detach),然后再创建一个新的操作系统的线程 (如果有空闲的线程可用就复用空闲线程) 来服务于这个 P;
  4. 当 M 系统调用结束时候,这个 G 会尝试获取一个空闲的 P 执行,并放入到这个 P 的本地队列。如果获取不到 P,那么这个线程 M 变成休眠状态, 加入到空闲线程中,然后这个 G 会被放入全局队列中。

调度器的生命周期

参考:

再见 Go 面试官:GMP 模型,为什么要有 P?_煎鱼(EDDYCJY)的博客-CSDN博客

Go-GMP模型 | XuChen的博客

深入理解GMP模型_cheems~的博客-CSDN博客_gmp模型

Go的CSP并发模型实现:M, P, G - sunsky303 - 博客园

Go语言GMP模型_码上腾飞的博客-CSDN博客

Golang并发模型之GMP - Go语言中文网 - Golang中文社区

关于Go语言,你不得不知的并发模式_博文视点的博客-CSDN博客

图解Go语言GMP模型_张大鹏520的博客-CSDN博客_gmp go


 

Have Fun

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值