您的位置:首页 > 编程语言 > Go语言

go语言goroutine调度

2020-05-04 12:15 567 查看

GO语言goroutine 的调度

go 语言是通过语言层面提供并发,即go语言的并发由go从代码内部开始

go func(***){***}

goroutine

goroutine 是一个与其他goroutine并发运行在同一地址空间的GO函数或方法,其本质是协程,是并行计算的核心,只需使用关键字go即可启动一个协程,且是处于异步的运行方式。

  1. 一个运行的程序是由一个或多个goroutine组成
  2. goroutine通过通道(channel)来通信。协程通过让出或恢复操作通信。
  3. 通过golang调度器进行调度,协程通过程序本身调度。o
简单来说就是golang自己实现了协程并叫作Goroutine,且比协程更强大
  • 并发

一个cpu上能同时执行多项任务,在短时间内,cpu来回切换任务执行,有时间上的重叠,这样看起来多个任务像同时执行。

  • 并行

当有多个cpu时,每个cpu同时都运行任务,互不抢占自己所在cpu的资源,同时进行。

goroutine调度模型

Go调度器内有四个重要结构:M,P,S,Sched:
1. G代表一个goroutine对象,每次go调用时,都会创建一个G对象。
2. M代表内核级线程,一个M就是一个线程,goroutine就跑在M(内核级线程)之上。
3. P代表一个处理器,每一个运行的M(内核级线程)都必须绑定一个P,就像线程必须在cpu上执行一样。
它主要用来执行goroutine,它也维护了一个goroutine队列,里面存储了所有需要它执行的goroutine.
4. sched代表调度器,维护存储M(内核级线程)和G(goroutine对象)的对了及调度器的状态。
需要注意的是
  1. P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个Goroutine可以同时运行(最大256)。

  2. 每一个P保存着本地G任务队列(RunQueue),同时会有一个全局G任务队列(Global RunQueue)。

每次go调用的时候
1. 创建一个G对象,加入到本地队列或者全局队列
2. 如果还有空闲的P(处理器),则创建一个M(内核级线程)
3. M(内核级线程)会启动一个底层线程,循环执行能找到的G(goroutine对象)任务
4. G(goroutine对象)任务的执行顺序是,先从本地队列找,本地没有则从全局队列找,
之后再去其它P(处理器)中找

以上的G任务执行是按照队列顺序(也就是go调用的顺序)执行的。

当线程阻塞时
1. 当一个OS线程M陷入阻塞时,P转而去运行其他M(1),M(1)可能是正被创建,或者从线程缓存中取出。
2. 当M返回时,它必须尝试取得一个P来运行Goroutine,一般情况下,它会从其他的OS线程那里拿一个P过来。
3. 如果没有拿到的话,它就把Goroutine放在Global RunQueue里,然后自己睡眠(放入线程缓存里)。
所有的P也会周期性的检查Global RunQueue并运行其中的Goroutine,
否则Global RunQueue上的Goroutine永远无法执行。

Goruntinue如何调度
1. 系统在启动的时候,会专门创建一个线程sysmon,用来监控和管理,在内部是一个循环:
2. 记录所有P的G任务计数schedtick,schedtick会在每执行一个G任务后递增
3. 如果检查到 schedtick一直没有递增,说明这个P一直在执行同一个G任务,如果超过一定的时间(10ms),
就在这个G任务的栈信息里面加一个标记
4. 当G任务在执行的时候,如果遇到非内联函数调用,就会检查一次这个标记,然后中断自己,把自己加到队
列末尾,执行下一个G
5. 如果没有遇到非内联函数(有时候正常的小函数会被优化成内联函数)调用的话,那就惨了,会一直执
行这个G任务,直到它自己结束;如果是个死循环,并且GOMAXPROCS=1的话,恭喜你,夯住了

所以Goroutine是按照抢占式调度的,一个Goroutine最多执行10ms就会换作下一个
G任务中断后的恢复

中断的时候将寄存器里的栈信息,保存到自己的G对象里面
当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了

菜猿猿 原创文章 6获赞 0访问量 142 关注 私信
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: