goroutine是go里的最基本的执行单元,每个go程序一开始都有一个主goroutine。
goroutine可以认为是轻量级的用户态线程,go里内核线程和用户线程的调度模型是M:N模型。
但是Go不直接将内核线程与goroutine绑定起来运行,而是通过一个上下文P来作为调度的中介,P提供了goroutine运行所需的一切资源和环境,所以在goroutine看来P就是运行它的 “CPU”。
一个内核线程M维护一个P,一个P维护一个本地G队列,同一时刻里,一个P里只有一个G在运行。
当通过go关键字创建一个新的G的时候,它会优先被放入P的本地队列。然后M从P的本地队列里取出一个goroutine并执行。它还有一个 work-stealing调度算法:当M执行完了当前P的本地队列里的所有G后,它会先尝试从全局队列寻找G来执行,如果全局队列也为空,它就会随机挑选另外一个P,从它的队列里中偷走一半的G到自己的队列中执行。
参考 go中的G.M.P以及Goroutine、Scheduler
如果一个G任务执行时间太长,它就会一直占用 M 线程,由于队列的G任务是顺序执行的,其它G任务就会阻塞,如何避免该情况发生?
go协程的切换时间片是10ms,也就是说 goroutine 最多执行10ms就会被 M 切换到下一个 G。
具体的过程参考Golang 的 协程调度机制 与 GOMAXPROCS 性能调优
如果一个G阻塞了,内核线程M也会跟着这个G进入睡眠状态,然后P带着它的G队列去投奔新的线程M,然后继续运行队列里的其他G。阻塞结束后,M需要去找一个P,然后尝试把这个G加入P的本地队列运行,或者加入全局队列。