您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

深入浅析Go语言中要有GMP调度模型的原因

2025/4/10 5:56:37发布47次查看
go为什么要有gmp调度模型?下面本篇文章给大家介绍一下go语言中要有gmp调度模型的原因,希望对大家有所帮助!
gmp调度模型是go的精髓所在,它合理地解决了多线程并发调度协程的效率问题。
gmp是什么首先得清楚,gmp各代指什么东西。
g: goroutine的缩写,指协程,运行在线程上。m: machine的缩写,即thead,线程,循环的调度协程并执行。p: processor的缩写,指处理器,将协程储存到本地队列,并为线程提供未休眠的可用的协程,线程m各自持有一个处理器p,当要获取协程时优先从p中获取,于是gmp模型图解如下:
大致流程是,线程m从p的队列中获取协程,如果获取不到,就会从全局队列中去竞争锁来获取。
处理器p协程g和线程m结构在前几篇已经讲解了,这里解析一下处理器p。
作用处理器p储存着一批协程,使得线程m可以无锁的从中获取协程,而无需与其他线程去竞争全局队列中的协程,从而提高调度协程效率。
源码解析p结构体源码在src\runtime\runtime2.go中,这里展示部分重要字段。
type p struct { ... m muintptr // back-link to associated m (nil if idle) // queue of runnable goroutines. accessed without lock. runqhead uint32 runqtail uint32 runq [256]guintptr runnext guintptr ...}
m为处理器p所属的线程runq是一个储存协程的队列runqhead,runqtail表示队列的头尾指针runnext指向下一个可运行的协程
线程m与处理器p是如何协作的?在src\runtime\proc.go中,有一个schedule方法,这是线程运行的第一个函数。这函数中,线程需要获取到可运行的协程,代码如下:
func schedule() { ... // 寻找一个可运行的协程 gp, inherittime, trywakep := findrunnable() ...}
func findrunnable() (gp *g, inherittime, trywakep bool) { // 从本地队列中获取协程 if gp, inherittime := runqget(pp); gp != nil { return gp, inherittime, false } // 本地队列拿不到则从全局队列中获取协程 if sched.runqsize != 0 { lock(&sched.lock) gp := globrunqget(pp, 0) unlock(&sched.lock) if gp != nil { return gp, false, false } }}
从本地队列中获取协程
func runqget(pp *p) (gp *g, inherittime bool) { next := pp.runnext // 队列中下一个可运行的协程 if next != 0 && pp.runnext.cas(next, 0) { return next.ptr(), true } ...}
那如果本地队列和全局队列中都没有协程了怎么办呢,难道就让线程这么闲着?
这时候处理器p就会任务窃取,从其他线程的本地队列中窃取一些任务,美其名曰分担其他线程的压力,还提高了自己线程的利用率。
源码在src\runtime\proc.go\stealwork中,感兴趣可以看看。
新建的协程该分配到哪?新建的协程该分配到本地还是全局队列呢,得分情况:
go认为新协程的优先级高,于是先寻找本地队列放入,而且还插队。本队队列满了才放入全局队列。实际流程为:
随机寻找p将新协程放入p的runnext中,意味着下一个就运行该协程,插队了若p的协程满了,则放入全局队列源码在src\runtime\proc.go\newproc函数中。
// create a new g running fn.// put it on the queue of g's waiting to run.// the compiler turns a go statement into a call to this.func newproc(fn *funcval) { gp := getg() pc := getcallerpc() systemstack(func() { newg := newproc1(fn, gp, pc) // 创建新协程 pp := getg().m.p.ptr() runqput(pp, newg, true) // 寻找本地队列放入 if mainstarted { wakep() } })}
结语本篇初步介绍了gmp调度模型,具体介绍了处理器p以及线程m获取协程的方式。
处理器p解决了多线程互斥获取协程的问题,提高调度协程的效率,但是不管协程在本地还是全局队列,目前看来还只是顺序执行,那go是如何实现协程异步并发执行的呢?咱下一篇继续分析(虽然没什么人看...)。
推荐学习:golang教程
以上就是深入浅析go语言中要有gmp调度模型的原因的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product