14.3、GPM调度器(了解)
    GPM是GO语言运行时(runtime)层面得实现,是go语言自己实现得一套调度系统 区别于操作系统调度得OS线程

    M指的是Machine,一个M直接关联了一个内核线程。由操作系统管理。 P指的是”processor”,代表了M所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文,将等待执行的G与M对接。 G指的是Goroutine,其实本质上也是一种轻量级的线程。包括了调用栈,重要的调度信息,例如channel等。

    P的数量由环境变量中的GOMAXPROCS决定,通常来说它是和核心数对应,例如在4Core的服务器上会启动4个线程。G会有很多个,每个P会将Goroutine从一个就绪的队列中做Pop操作,为了减小锁的竞争,通常情况下每个P会负责一个队列。
    Goroutine调度策略
    每次调用go的时候,都会:

    1. /*
    2. A、创建一个G对象,加入到本地队列或者全局队列
    3. B、如果有空闲的P,则创建一个M
    4. C、M会启动一个底层线程,循环执行能找到的G任务
    5. D、G任务的执行顺序是先从本地队列找,本地没有则从全局队列找(一次性转移(全局G个数/P个数)个,再去其它P中找(一次性转移一半)。
    6. E、G任务执行是按照队列顺序(即调用go的顺序)执行的。
    7. */

    创建一个M过程如下:

    1. /*
    2. A、先找到一个空闲的P,如果没有则直接返回。
    3. B、调用系统API创建线程,不同的操作系统调用方法不一样。
    4. C、 在创建的线程里循环执行G任务
    5. */

    如果一个系统调用或者G任务执行太长,会一直占用内核空间线程,由于本地队列的G任务是顺序执行的,其它G任务就会阻塞。因此,Go程序启动的时候,会专门创建一个线程sysmon,用来监控和管理,sysmon内部是一个循环:

    1. /*
    2. A、记录所有P的G任务计数schedtick,schedtick会在每执行一个G任务后递增。
    3. B、如果检查到 schedtick一直没有递增,说明P一直在执行同一个G任务,如果超过一定的时间(10ms),在G任务的栈信息里面加一个标记。
    4. C、G任务在执行的时候,如果遇到非内联函数调用,就会检查一次标记,然后中断自己,把自己加到队列末尾,执行下一个G。
    5. D、如果没有遇到非内联函数(有时候正常的小函数会被优化成内联函数)调用,会一直执行G任务,直到goroutine自己结束;如果goroutine是死循环,并且GOMAXPROCS=1,阻塞。
    6. */

    (1) 局部优先调度
    (2) steal working
    (3) 阻塞调度
    (4) 抢占式调度