go中gmp调度模型

gmp模型:

gmp是调度层面的实现, 包含4个结构, 分别是g, m, p, sched
go中gmp调度模型_第1张图片


g(goroutine):代表go协程goroutine, 存储了goroutine的执行栈信息,
Goroutine状态以及Goroutine的任务函数等, G的数量无限制,
理论上只受内存影响, 创建一个G的初始栈大小为2-4K, 配置一般机器
也能开启数十万个Goroutine, 而且go语言在G退出的时候, 还会把G清理
之后放到P本地或者全局的闲置列表中。


M(machine):go对操作系统线程的封装, 可以看做是操作系统内核线程,
想要在cpu上执行代码必须有线程, 通过系统调用clone创建, M在绑定有效
p之后,进行循环调度, 而循环调度的机制大致是从p的本地运行队列以及全局
队列中获取G, 切换到G的执行栈上执行G的函数,
调用goexit做清理工作并回到M, 如此反复。
M并不保留G状态, 这是G可以跨M调度的基础, M数量有限制,
默认数量是10000, 可以通过debug.SetMaxThreads()来进行设置,
如果M空闲, 那么就会回收或者睡眠。


P(processor):虚拟处理器, M执行G所需要的资源和上下文, 只有将p和
m绑定, 才能让p的runq中的G真正运行起来, p的数量决定了系统最大
可并行G的数量, p数量受本机cpu核数影响, 可通过环境变量
$GOMAXPROCS或者在runtime.GOMAXPROCS()来设置, 默认为cpu核心数。


sched:调度器结构, 它维护存储m和G的全局队列, 以及调度器的一些状态信息。
go中gmp调度模型_第2张图片

gmp是实现go调度器的一大进步, 但是仍然有问题。
就是不支持抢占式调度, 一旦某个g出现死循环, 那么G将永远占用分配给
它的P和M, 而位于同一个P中的其他G将得不到调度,
出现饿死现象。
在go1.2版本实现了基于协作式的抢占式调度,
go1.14版本实现了基于信号的抢占式调度。


gm模型:早期是gm模型, 没有p组件。
go中gmp调度模型_第3张图片

1.全局队列的锁竞争,
2.M转移G会增加开销。
3.线程使用效率不能最大化。


go中调度机制
cpu感知不到goroutine, 只知道内核线程, 所以需要go调度器将
协程调度到内核线程上去, 然后操作系统调度器将内核线程放到cpu上
去执行。

M是对内核线程的封装, 所以go调度器就是将G分配到M

go调度器实现不是一下就好的, 经历了GM模型, 到gMP模型,
从不支持抢占, 到支持协作式抢占, 经历了不断优化和打磨。
涉及思想:
1.线程复用(work stealing 和hand off机制)
2.利用并行(多核cpu)
3.抢占式调度(解决公平问题)


协程的调度采用了生产者-消费者模型, 实现了用户任务和调度器的解耦。
go中gmp调度模型_第4张图片

go中gmp调度模型_第5张图片

步骤1:创建G, 关键字go func()创建G
步骤2:保存G, 创建的G优先保存到本地队列P,如果p满了, 则会平衡部分p到全局队列中。
步骤3:唤醒或者新建M执行任务, 进入调度循环
4:M获取G, M先从P的本地队列中获取G, 如果P为空, 则从全局队列中获取G, 如果全局队列也
为空, 从另一个本地队列偷取一半数量的G, 这种方式叫做work stealing.
5: M调度和执行G, 调用G.func(), 函数执行G.
如果M在执行G过程发生系统调用阻塞, 会阻塞G和M, 此时p会和当前M解绑, 并且寻找新的M,
如果没有新的M, 就会新建一个M, 接管正在阻塞G所属的P, 接着执行p中其余的G,
这种阻塞后释放p的过程就是 hand off, 当系统调用结束后, 这个G会尝试获取一个空闲p执行,
优先获取之前绑定的P, 并放入到这个p的本地队列。如果获取不到P, 那么这个M编程休眠状态,
加入到空闲线程中, 然后这个G就会被放入到全局队列中。
如果M在执行G过程中发生网络IO等操作阻塞时(异步), 阻塞G, 不会阻塞M,
M会寻找p中其他的可执行G继续执行, G会被网络轮询器network poller接手,
当阻塞的G恢复后, G1从network poller移动回到p的LRQ中, 重新进入可执行状态,
异步情况下,通过调度, go scheduler成功将IO任务转变成了cpu任务, 或者说
将内核级别线程切换成了用户级别goroutine, 大大提高效率。
6. M执行完G后清理现场, 重新进入调度循环。
go中gmp调度模型_第6张图片


gm模型:

垃圾回收器是需要stop the worlld的, 如果垃圾回收器想运行,
必须先通知它的goroutine停下来, 这会造成长时间等待。

2种方式查看程序的gmp调度分别是, go tool trace 和godebug

你可能感兴趣的:(go常见面试题,golang,开发语言,后端)