golang语言特性就是以并发著称,golang的并发好跟它的调度系统不无关系。本文主要总结一下个人对golang调度的理解。
主要从几个方面进行总结(针对现如今流行的多核cpu架构):
- 操作系统的线程调度
- golang会在什么时候进行调度决策
- golang是如何进行调度的
操作系统的调度
比如一个具有8个线程的程序在一台4核八线程的计算机上运行,这个理论上8个线程是可以同时被执行的,但是这个程序超过了8个线程的话,那么其余的线程就必须需要等待操作系统的调度了,等待其他线程让出cpu的时间片,才有机会执行。
golang会在什么时候进行调度决策
以下几点将会导致golang调度系统的进行调度决策:
- 使用go关键字
- golang进行GC的时候
- 进行系统调用的时候
- 同步处理的时候
golang是如何进行调度的
golang的调度模型是PMG模型
假如我的计算机是4核八线程,任何一个go程序会有八个 虚拟内核可以利用,也就是有八个P,每个P都会分配一个M,M是操作系统级别的线程,这就意味着任何一个golang程序就有8个可用的线程去处理。
每个go程序都会有一个初始的goroutine(也就G),goroutine也称为用户态线程。
还有一个重要的是,golang的调度中有两个重要的概念:本地运行队列与全局运行队列。每个P都会绑定一个本地运行队列。
下重点介绍一下,go程序在进行系统调用时是如何调度的,进行系统调用又分为异步系统调用与同步系统调用。
异步系统调用
当您运行的操作系统能够异步处理系统调用时,golang的调度器可以防止Goroutine在进行系统调用的时候阻塞M,这个时候M可以去执行绑定的P的goroutine本地运行队列的其他Goroutine,而不用让当前的P解绑后与新建一个M,或者别的空闲的M绑定后再执行goroutine本地运行队列。一但系统调用完成之后,该goroutine会再次进入本地运行队列进行排队,等待执行
同步系统调用
但是当一个goroutine-1进行系统调用却又不能异步进行时怎么办呢?这个时候goroutine-1会导致当前M-1阻塞,调度器会将P-1与M-1解绑,将P-1重新挂到空闲的M-2,或者其他M上,继续执行P-1绑定的本地运行队列的其他G,值得注意的是,此时G-1还在M-1上等待调用返回,调用完成之后,重新回到本地队列进行排队,等待执行。
还有一个调度场景,就是golang调度的抢占式调度。比如有两个M, M1与M2分别对应着P1、P2(默认情况下是使用一个cpu核的,P的数量可以通过GOMAXPROCS()来设置),对应相应的两个本地运行队列。假如M1已经处理完它相应的G,而临近的M2还没有完成处理,则P1就会去P2绑定的本地运行队列中强一般的G来运行,M1与M2都处理完了,P1与P2就会去全局运行队列中取G,再去进行执行。
以上就是golang的调度总结,比较粗糙,但是也算能供自己去复盘理解