浅谈Go语言调度机制(二)| 青训营笔记

78 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天.

接着上次,继续讲一下Go语言调度机制的Go sheduler。

Go语言的程序在执行时分为两个层面,即Go Program 以及Go Runtime。Program和Runtime之间通过函数调用实现众多功能:goroutine间的channel通信、内存管理、goroutine创建等。所有的用户程序进行期间的系统调用都会被这个Go Runtime拦截下来,目的是为了帮助这个程序进行调度以及垃圾回收。Runtime会维护所有的goroutine,并且通过scheduler进行调度。

Goroutine的调度时机

goroutine调度基本上集中在以下四种情况(不必然调度,只是有可能)

第一,使用关键字go创建一个新的goroutine,此时goroutine有可能发生调度。

第二,GC垃圾回收。由于GC的goroutine也需要在M上运行(此时是必然调度),但是GC的调度不涉及到堆访问的goroutine,GC不会回收栈上的内存,只会回收堆的内存。

第三,系统调用。当goroutine进行系统调用时,会阻塞M,所以此时的goroutine会被调度,与此同时一个新的goroutine会被调度上来填补空位。

第四,内存同步访问。原子操作(atomic),锁操作(mutex),channel 等都会使goroutine阻塞,因此会被调度走,等其他goroutine解锁了再被调度上来继续运行。

M:N模型

所谓的M:N模型就是,Go runtime负责goroutine的从创建到销毁,Go runtime会在程序运行后,按需创建N个线程,之后再创建M个goroutine依附在这N个线程上执行。在同一时刻,一个线程只能跑一个goroutine,当goroutine发生阻塞时,runtime会把当前的goroutine调度,让其他的goroutine来执行。

工作窃取

Go scheduler的职责是将所有的处于runnable的goroutine均匀调度到在P上运行的M。当一个P发现自己的LRQ没有G时,会从其他的P去窃取一些G来运行,这就是工作窃取。

Go scheduler每一轮调度的工作内容就是找到处于runnable的goroutine并执行。那么是如何去找到这个处于runnable的goroutine呢?顺序如下:

一,从本地可运行队列寻找。

二,从全局可运行队列找。

三,从netpoll找。

四,从其他的P窃取。