<返回更多

Golang调度器

2020-05-05    
加入收藏

GO调度器模型

GO的调度器可以充分利用多核心CPU,任何时候都有M个go协程在N个系统线程上进行调度, 这些线程在最多 GOMAXPROCS 个CPU核心上运行,这种调度模型称之为GMP模型:

如下图,每个P都有一个本地队列存放待运行的G,另外有一个全局队列,每个M需要依附在P上运行,一个P可以对应多个M, 但是同一时间一个P上只会有一个正在运行的M。

Golang调度器

 

每轮调度只需要找一个可运行的G执行它就行,这个查找过程如下:

runtime.schedule() {
    // 1/61 的概率去全局队列找一个G来运行
    // 如果没有找到,到本地队列中找
    // 如果没有找到,
    //     尝试从其他的P中偷取G来运行
    //     如果没有,检查全局队列
    //     如果没有,检查 Net Poller
}

GO协程的状态

GO协程和线程一样有三种状态,一个协程可以处于下面三种状态中的一种: Waiting , Runnable 和 Executing 。

调度时机

GO程序中的下列4类事件可以触发调度器执行调度任务。并不是说这些事件发生时调度器一定会执行调度,只是说此时调度器有机会执行调度:

异步调用

大多数操作系统都支持网络轮询,例如MacOS的kqueue,linux的epoll接口。Go会利用网络轮询接口来异步处理网络请求, 当G调用网络系统调用时调度器会将此G调度出去以避免M被阻塞,然后调度队列中的其他G继续执行,因此不需要创建一个新的M,减少调度开销。

在图1中,Goroutine-1正在M上运行,此时本地队列中有3个G在等待运行,网络轮询器上是空的。

Golang调度器

 

图2中,Goroutine-1希望进行网络系统调用,此时将Goroutine-1移至网络轮询器上处理异步网络系统调用然后将Goroutine-2调度到M上继续运行。

Golang调度器

 

图3中,网络调用完成,此时Goroutine-1被放回本地队列中,当调度到Goroutine-1时,它可以继续执行接下来的指令,这里最大的好处是执行网络系统调用不需要额外的M, 网络轮询器实际是一个系统线程专门用于处理异步网络请求。

Golang调度器

 

同步调用

当G调用同步系统调用时会怎样?例如文件相关的系统调用以及使用CGO时调用C函数也是同步调用,此时M会被此G阻塞。

图4中,Goroutine-1正在M1上运行,他要执行同步系统调用,此时会阻塞M1。

Golang调度器

 

图5中,调度器可以探测出M1被Goroutine-1阻塞了,此时调度器会将M1和P分开,但是Goroutine-1还是在M1上。 然后搞一个M2继续在P上运行,此时可以调度Goroutine-2继续执行。GO会维护一个线程池只有线程池中没有M才会创建新的M,所以这种M切换是非常快的。

Golang调度器

 

图6中,Goroutine-1的同步阻塞调用完成,此时Goroutine-1会被转移到P的本地队列中待被调度执行,此时M1会被放在线程池待下次使用。

Golang调度器

 

任务窃取

任务窃取作用是平衡P之间的负载,如果某个P上的G都执行完了,此时会检查其他P上有没有可执行的G,如果有则会窃取其他P上的G来执行。

图7中,有两个P,每个P上有4个G,全局队列中也有一个G。

Golang调度器

 

图8中,P1上的G全部执行完了,但是P2和全局队列上还有G待执行。此时P1需要窃取其他G来执行,窃取规则和调度规则是一样的参考上面的 runtime.schedule 。

Golang调度器

 

图9中,根据窃取规则,P1会将P2上一半的G窃取过来执行。

Golang调度器

 

图10中,如果此时P2上的G都执行完了,并且P1的本地队列中也没有G了会怎么办?

Golang调度器

 

图11中,P2上的G都执行完,它要开始窃取任务,但是P1上也没有G了,根据窃取规则他会把全局队列上的G拿过来执行。

Golang调度器

 

参考

这篇文章基本上是翻译下面的文章,然后加了一些自己的理解。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>