前段时间的某个周末粗粗地阅读了golang中运行时调度的部分,过了都快一个月了,一直没有记笔记,再不记怕自己都记不清了,所以在自己还大概记得清的时候赶快记录一下。
因为对源码读的不够深入且阅读的时间很短,所以理解很可能有误,请大家指正。
golang运行时部分的代码在src/pkg/runtime下,要看goroutine的调度,主线主要在proc.c这个文件里(golang的源码写的确实精彩,特别是注释写的很棒,不多不少把问题解释的清清楚楚)。
为了说明运行时调度,我按照自己的理解画了下面这张图

                

                
                
                
运行时的状态、对象
运行时的状态、对象

                

                
其中:
    M是golang中对cpu的抽象,即一种可以执行代码的对象(实际是用线程来实现的);
    g就是golang对goroutine的抽象,是golang中的调度单位;
    此外,我们知道golang中GOMAXPROCS可以控制同时运行的goroutine的最大数量,在图中我使用了Running方框来表示(这也很好的解释了为什么即使我们讲GOMAXPROCS设置为1,我们查看后台线程也会发现不只一个,这是因为还有处于其他状态的M在运行,当然gc也会使用一个线程)。

在golang中实际上就是将g放到M上去执行,在一定的时机(目前我看到的时机主要有两个,一个是主动发起Gosched调用,另外一种就是goroutine进入syscall)M会将自己身上放着的g进行切换,换成另外一个Ready的g执行,这就是golang中对goroutine的调度了。
下面以主动发起Gosched为例,说明一下goroutine的调度:

                

                
                
                
调用Gosched
调用Gosched

                

                
如上图所示,整个过程分为这样几步:
1. 获取全局对象shed,锁住调度,确保同时只有一个调度在发生;
2. 把当前m上的g放到Ready队列里;
3. 从Ready状态的g中选出一个来执行(当然2、3步中会涉及到对goroutine状态的保存与恢复,其实过程与线程的切换比较像,在golang里这一部分是用汇编实现的)
4. 释放sched

此外golang中在goroutine进入syscall时也会发生调度,此时如果处于running的m不够GOMAXPROCS个,runtime会从blocked的M中(或者时新建一个M)拿出一个进入running运行;在从syscall返回时,如果还不到GOMAXPROCS的限制,则会直接返回到running里运行,否则M和g会进行剥离,g会放到Ready里,M则被block。(本来这一部分也想画画图,说的更清楚,但是发现没鼠标画图太累,就大概说说吧)

理解了这些,可以解释很多我们碰到的现象,比如当我们使用多个goroutine发起长时间阻塞的系统调用,可以看见产生了很多的线程(每个阻塞的系统调用都会占用一个线程)
说明:以上部分的阅读涉及到平台实现相关的部分,我都是读的linux的实现。再次申明,这是个人的理解,很有可能有误,请大家指正。
by芝麻大盗,转载请注明