社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
正确地认识 G , M , P 三者的关系,能够对协程的调度机制有更深入的理解! 本文将会完整介绍完 go 协程的调度机制,包含:
Golang 简称 Go,Go 的协程(goroutine)
和我们常见的线程(Thread)
一样,拥有其调度器。
go 关键词
时候会创建的一个对象处理器
,又称上下文队列
队列中
提取 G,并执行GOMAXPROCS
(最大256),启动时固定的,一般不修改协程任务
交换全局队列
找当我们创建一个G对象,就是 gorutine
,它会加入到本地队列或者全局队列
如果还有空闲的P,则创建一个M 绑定该 P ,注意!这里,P 此前必须还没绑定过M 的,否则不满足空闲的条件。细节点:
先找到一个空闲的P,如果没有则直接返回
P 个数不会占用超过自己设定的cpu个数
P 在被 M 绑定后,就会初始化自己的 G 队列,此时是一个空队列
注意这里的一个点
!
这里留下第一个问题:
如果一个G任务执行时间太长,它就会一直占用 M 线程,由于队列的G任务是顺序执行的,其它G任务就会阻塞,如何避免该情况发生? --①
M 会启动一个底层线程
,循环执行
能找到的 G 任务。这里的寻找的 G 从下面几方面找:
G任务的执行顺序是,先从本地队列找,本地没有则从全局队列找
程序启动的时候,首先跑的是主线程,然后这个主线程会绑定第一个 P
入口 main 函数,其实是作为一个 goroutine 来执行
协程的切换时间片是10ms,也就是说 goroutine 最多执行10ms就会被 M 切换到下一个 G。这个过程,又被称为 中断,挂起
原理:
go程序启动时会首先创建一个特殊的内核线程 sysmon
,用来监控和管理,其内部是一个循环:
记录所有 P 的 G 任务的计数 schedtick
,schedtick会在每执行一个G任务后递增
如果检查到 schedtick
一直没有递增,说明这个 P 一直在执行同一个 G 任务,如果超过10ms,就在这个G任务的栈信息里面加一个 tag 标记
然后这个 G 任务在执行的时候,如果遇到非内联函数调用,就会检查一次这个标记,然后中断自己,把自己加到队列末尾,执行下一个G
如果没有遇到非内联函数
调用的话,那就会一直执行这个G任务,直到它自己结束;如果是个死循环,并且 GOMAXPROCS=1 的话。那么一直只会只有一个 P 与一个 M,且队列中的其他 G 不会被执行!
例子,下面的这段代码,hello world
不会被输出
func main(){
runtime.GOMAXPROCS(1)
go func(){
fmt.Println("hello world")
// panic("hello world") // 强制观察输出
}()
go func(){
for {
// fmt.Println("aaa") // 非内联函数,这行注释打开,将导致 hello world 的输出
}
}()
select {}
}
看完上面的内容,相信你已经知道,GOMAXPROCS
就是 go 中 runtime 包的一个函数。它设置了 P 的最多的个数。这也就直接导致了 M 最多的个数是多少,而 M 的个数就决定了各个 G 队列能同时被多少个 M 线程来进行调取执行!
故,我们一般将 GOMAXPROCS 的个数设置为 CPU 的核数,且需要注意的是:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!