go 调度器实现

2021-12-12  本文已影响0人  jason24L

GO 语言的调度器

目录

GMP 模型简介

先来一张经典的GMP 关系图


img

调度器的实现机制

package main

func main(){
    ch := make(chan int)
    go func(){
        ch <- 1
    }
    <-ch
}

只要在方法前面加上关键字go就能很容易的开启一个goroutine, 如上 述代码所示, 是不是so easy。可他究竟是怎么调度的呢?先来看下Go 程序运行的结构,Go 程序主要两部分构成 用户层和运行层,见下图

img

在Runitme 层有三个基础的结构体runtime.G, runtime.M, runtime.P, 在加上一个核心的结构体runtime.Sche 来实现goroutine的调度保证Go 程序的高效执行,下面介绍它是如何高效的调度的。

img

Go 的调度器是有两个goroutine队列 的,一个是放在P 上的Local 队列,一个是放在sche上的全局队列。早期的版本确实只有一个全局队列,但当并发比较大的时后多个M获取G时导致加锁解锁的开销比较大,后来的版本就引入了P, 在P上有一个Local队列从而减少锁的开销。当开启一个goroutine时,先把G放到P上的队列当中,等需要执行G时,P需要绑定一个M,将G运行在M上。未分配给P的Goroutine则放到全局队列当中。

每个goroutine执行的快慢不一样,会产生有的P很忙,有的P很闲。 调度器让空闲的P偷其他P上goroutine到自己的队列上,其他p上也没有则从全局队列中偷取,从而充分利用了计算机上的每一个cup高效的执行任务。(P:想偷懒都不行了 哈哈)

当一个goroutine执行一些系统任务阻塞了M挂起来怎么办呢?下面从同步和异步分析下

先分析下同步的情况,当G1 进行系统调用时,M1就被阻塞了,G1挂在M1上, M2被引入了到P上,G2继续被调度。当G1的系统调用完成以后G1又会被放入P的LRQ当中,等待被调度执行。


img

再来看下异步的情况,当G1进行网络读写时,G1的异步请求会被netpoll接手,这时候M是没有被阻塞的,G2会被继续执行。等G1的异步请求完成以后G1 又会重新放到P的LRQ当中,等待被调度执行。


img

总结

上一篇下一篇

猜你喜欢

热点阅读