GMP学习笔记

整理自:Golang深入理解GPM模型

总结

掌握Golang协程调度器的原理,为什么Go的协程调度那么快?

Go的调度器做了很多事情来避免过多的操作系统线程抢占,通过窃取调度(stealing机制)它们到正确的和未充分利用的处理器,以及实现自旋线程以避免过高阻塞或者解除阻塞切换的发生。

一、Golang “调度器”的由来

1.单进程时代的问题

​ 1). 单一执行流程,计算机只能一个任务一个任务的执行;

​ 2).进程阻塞所带来的CPU浪费时间;

2.多进程、多线程的问题

​ 1).设计变得复杂;进程/线程数量越多,切换成本越大,也就更浪费;

​ 2).同步竞争,如锁、竞争资源等冲突;

3.多进程、多线程的壁垒:高内存占用,高CPU调度切换;

4.协程(co-routine)引发的问题

​ N:1 : 无法利用多个CPU,出现阻塞瓶颈;

​ 1:1 :和多线程、多进程无区别,切换协程成本代价昂贵;

​ M:N :能够利用多核,但过度依赖协程调度器的优化和算法;

二、Groutine调度器的设计思想

GMP模型简介

G:Groutine 协程, go程序建立的用户线程。

M:machine内核线程,每个M都有一个线程的栈。

P:processor 处理器

全局队列

存放等待运行的groutine

P的本地队列

存放等待运行的G;数量限制,不超过256G;优先将创建的G放在P的本地队列中,满了之后才会放入全局队列;

P列表

程序启动时创建;最多有GOMAXPROCS个(可配置);

M列表

当前操作系统分配到当前Go程序的内核线程数;有一个M阻塞,会创建或者唤醒一个新的M,若有空闲,则会回收或睡眠此M;

调度器的设计策略

复用线程

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

Work stealing机制

当本线程的P队列中没有G可用并且全局队列为空时,会主动去其他线程的P队列中获取,避免线程的销毁;

handle off 机制

当本线程的G运行阻塞时,会释放绑定的P队列,把P队列转移到其他空闲的线程上去;

利用并行

GOMAXPROCS设置P的数量,最多有GOMAXPROCS个线程分布在CPU上运行;

抢占

一个Goroutine 最多占用CPU 10ms 就会交给其他 协程,防止其被饿死;

全局G队列

当M执行work stealing 机制时,无法从其他P中获取G,就会从全局队列中获取;

调度器的生命周期

M0 :

启动程序后编号为0的主线程,负责执行初始化操作和启动第一个G,启动第一个G后,M0就和其他M一样了

G0:

每次启动一个M,都会创建一个groutine,这就是G0;G0仅用于调度其他的G;G0不指向任何可执行的函数,每个M都会有自己的G0;在调度和系统调用时会使用到G0的栈空间来调度;

M0和G0会放在全局空间;全局变量的G0是M0的G0,一般M的G0会放在自己的本地队列中;

可视化的GMP编程

基本的trace编程

1.创建trace文件 c, err := os.Create("trace.out")

2.启动 trace.Start(c)

3.停止trace.Stop()

4.go build运行后,会得到一个trace.out文件

5.通过go tool trace打开trace文件,go tool trace trace.out

通过 debug trace 查看 GMP 信息

GODEBUG=schedtrace=1000 ./可执行程序

三、Go调度器GMP调度场景的过程分析

场景一:创建G

P拥有G1,M1获取P后开始运行G1,G1使用go func()创建了G2,为了局部性G2优先加入到P的本地队列中。

场景二:G1执行完毕

G1运行完成后(函数goexit),线程M上运行的groutine切换为G0,G0负责调度协程的切换(函数:schedule),从P的本地队列中取出G2,从G0切换到G2,并开始运行G2(函数:execute),实现了线程M的复用。

场景三、四、五:G2创建多个G

如果创建多个G,首先会把本地P队列装满,如果本地队列已满但还有未创建完的G,会将本地对列中的前一半G打乱顺序和刚创建的G一起放入全局队列中,还有未创建的G时,就将加入空出来的本地队列。

场景六:唤醒正在休眠的M

规定:在创建G时,运行的G会尝试唤醒其他空闲的P和M的组合去执行。

假定G2唤醒了M2,M2绑定了P2,并运行G0,但P2本地队列没有G,M2此时为自旋线程(没有G但是为运行状态的线程,不断寻找G)

场景七:被唤醒的M2从全局队列批量获取G

M2尝试从全局队列(简称"GQ")取一批G放到P2的本地队列(函数:findrunnable()),M2从全局队列取的G数量符合下面的公式:

n=min(len(GQ)/GOPAXPROCS+1,len(GQ/2))

解释:n 等于 最小值 ( 全局队列个数 除以 P 的个数 加 1 , 全局队列除以2的个数 )

当M2获取到G时,G0调度G运行后,将不再是自旋线程;从全局队列到P的本地队列这一过程就是GMP内部的负载均衡

场景八:M2从M1中偷去G

当全局队列中没有G,那么 M2 就要执行 work stealing 机制,从其他有G的P队列中偷取后面的一半,放在自己的P本地队列中。

场景九:自旋线程的最大限制

任何时候最多有 GOMAXPROCS 个自旋的M,当一个自旋线程找到工作时,它就脱离了自旋。

如果有空闲的M没有绑定P,那么被绑定P的空闲线程不会被阻塞,当新的G被创建或阻塞时,调度器确保至少有一个自旋M,这保证了没有可运行的G没被运行,并且避免过多的M阻塞或解除阻塞。

场景十:G发生系统调用/阻塞

当G1在M1阻塞时,G1会在M1上继续等待,但会唤醒一个休眠的M2,将M1绑定的P1转移至M2,继续执行P1本地队列中的G2。

自旋线程依然自旋,自旋线程只会偷去G,而不会去偷去P,因为它本身就有自己空闲的P。

场景十一:G发生非阻塞

接场景十,当G1非阻塞时,M1会尝试去寻找空闲的P进行绑定,以继续执行G1,当找不到可供绑定的空闲P时,G1会被加入全局队列,供其他P获取,M1会进入休眠状态。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值