场景1.G1创建G2

go func()

局部性:G2 优先加入到 P1 的本地队列

场景2.G1执行完毕

G1 运行完成后 (函数:goexit),M 上运行的 goroutine 切换为 G0,G0 负责调度时协程的切换(函数:schedule)。从 P 的本地队列取 G2,从 G0 切换到 G2,并开始运行 G2 (函数:execute)。实现了线程 M1 的复用。

场景3.G2开辟过多的G

场景4.G2本地满再创建G7

本地队列的前一半转移到全局队列(防止饥饿)

这些 G 被转移到全局队列时,会被打乱顺序。所以 G3,G4,G7 被转移到全局队列。

场景5.G2本地未满创建G8

G8 加入到 P1 点本地队列的原因还是因为 P1 此时在与 M1 绑定,而 G2 此时是 M1 在执行。所以 G2 创建的新的 G 会优先放置到自己的 M 绑定的 P 上。

场景6.唤醒正在休眠的M

在创建 G 时,运行的 G 会尝试唤醒其他空闲的 P 和 M 组合去执行。

假定 G2 唤醒了 M2,M2 绑定了 P2,并运行 G0,但 P2 本地队列没有 G,M2 此时为自旋线程(没有 G 但为运行状态的线程,不断寻找 G)。

场景7.被唤醒的M2从全局队列取批量G

        自旋线程M2,优先从全局队列获取一批G放到P2的本地队列(函数:findrunnable()).M2从全局队列取的G数量符合下面的公式

n=min(len(GQ)/GOMAXPROCS+1,len(GQ/2))

场景8.M2从M1偷取G

取后半部分

场景9.自旋线程的最大限制

自旋线程+执行线程 ≤ GOMAXPROCS

超过的线程将加入休眠线程队列

场景10.G发生系统调用/阻塞

        假定当前除了 M3 和 M4 为自旋线程,还有 M5 和 M6 为空闲的线程 (没有得到 P 的绑定,注意我们这里最多就只能够存在 4 个 P,所以 P 的数量应该永远是 M>=P, 大部分都是 M 在抢占需要运行的 P),G8 创建了 G9,G8 进行了阻塞的系统调用,M2 和 P2 立即解绑,P2 会执行以下判断:如果 P2 本地队列有 G、全局队列有 G 或有空闲的 M,P2 都会立马唤醒 1 个 M 和它绑定,否则 P2 则会加入到空闲 P 列表,等待 M 来获取可用的 p。本场景中,P2 本地队列有 G9,可以和其他空闲的线程 M5 绑定。

场景11.G发生系统调用/非阻塞

        G8由阻塞变为非阻塞

         M2 和 P2 会解绑,但 M2 会记住 P2,然后 G8 和 M2 进入系统调用状态。当 G8 和 M2 退出系统调用时,会尝试获取 P2,如果无法获取,则获取空闲的 P,如果依然没有,G8 会被记为可运行状态,并加入到全局队列,M2 因为没有 P 的绑定而变成休眠状态 (长时间休眠等待 GC 回收销毁)。