GMP模型
Goroutine主要概念如下:
G(Goroutine): 即Go协程, 每个go关键字都会创建一个协程。
M(Machine): 工作线程, 在Go中称为Machine。
P(Processor): 处理器(Go中定义的一个摡念, 不是指CPU), 包含运行Go代码的必要资源, 也有调度
goroutine的能力。
M必须拥有P才可以执行G中的代码, P有一个由G组成的队列, P可以调度G交由M执行。
P的个数在程序启动时决定, 默认情况下等同于CPU的核数, 由于M必须持有一个P才可以运行Go代码, 所以同时运行的M个数,也即线程数一般等同于CPU的个数,以达到尽可能的使用CPU而又不至于产生过多的线程切换开销。程序中可以使用 runtime.GOMAXPROCS() 设置P的个数。
调度策略
1.队列轮转:
不考虑G进入系统调用或IO操作的情况下, P周期性的将G调度到M中执行,执行一小段时间, 将上下文保存下来,然后将G放到队列尾部,然后从队列中重新取出一个G进行调度。除了每个P维护的G队列以外,还有一个全局的队列,每个P会周期性的查看全局队列中是否有G待运行并将期调度到M中执行,全局队列中G的来源, 主要有从系统调用中恢复的G。 之所以P会周期性的查看全局队列, 也是为了防止全局队列中的G被饿死。
2.系统调用:
P的个数默认等于CPU核数, 每个M必须持有一个P才可以执行G, 一般情况下M的个数会略大于P的个数, 这多
出来的M将会在G产生系统调用时发挥作用。 类似线程池, Go也提供一个M的池子, 需要时从池子中获取, 用完放回池
子, 不够用时就再创建一个。
3.工作量窃取:
P已经将G全部执行完, 然后去查询全局队列, 全局队列中也没有G, 而另一个M中除了正在运行的G
外, 队列中还有3个G待运行。 此时, 空闲的P会将其他P中的G偷取一部分过来, 一般每次偷取一半。
状态转换
https://zhuanlan.zhihu.com/p/261057034
G的主要8种状态
_Gidle(哎得/闲置):刚刚被分配并且还没有被初始化,值为0,为创建goroutine后的默认值
_Grunnable(run额bol/就绪状态): 没有执行代码,没有栈的所有权,存储在运行队列中,可能在某个P的本地队列或全局队列中(如上图)。
_Grunning: 正在执行代码的goroutine,拥有栈的所有权(如上图)。
_Gsyscall(谁是阔):正在执行系统调用,拥有栈的所有权,与P脱离,但是与某个M绑定,会在调用结束后被分配到运行队列(如上图)。
_Gwaiting:被阻塞的goroutine,阻塞在某个channel的发送或者接收队列(如上图)。
_Gdead(dei 的/死的): 当前goroutine未被使用,没有执行代码,可能有分配的栈,分布在空闲列表gFree,可能是一个刚刚初始化的goroutine,也可能是执行了goexit退出的goroutine(如上图)。
_Gcopystac:栈正在被拷贝,没有执行代码,不在运行队列上,执行权在
_Gscan (死看/扫描): GC 正在扫描栈空间,没有执行代码,可以与其他状态同时存在
P的5状态:
_Pidle :处理器没有运行用户代码或者调度器,被空闲队列或者改变其状态的结构持有,运行队列为空
_Prunning :被线程 M 持有,并且正在执行用户代码或者调度器(如上图)
_Psyscall:没有执行用户代码,当前线程陷入系统调用(如上图)
_Pgcstop :被线程 M 持有,当前处理器由于垃圾回收被停止
_Pdead :当前处理器已经不被使用
M的状态
自旋线程:处于运行状态但是没有可执行goroutine的线程(如下图),数量最多为GOMAXPROC,若是数量大于GOMAXPROC就会进入休眠。
非自旋线程:处于运行状态有可执行goroutine的线程。