1.Go 调度模型 GPM
首先需要明白概念 :
G是goroutine的缩写,保存goroutine的一些状态信息以及CPU的一些寄存器的值,相当于操作系统中的进程控制块。
M是machine的缩写,是一个线程。所有的M有线程栈,每一个M对应着一条操作系统的物理线程,承载G的队列。
P是processer的缩写,在这里是一个抽象概念,不是真正的物理CPU。P为M的执行提供了“上下文”,保存M执行G时的一些资源。
G P M 三者互相依赖,goroutine需要在processor上才能运行,processor为machine提供资源,processor持有待运行的G。
并发:在系统中,某一个时间段有几个程序都处于已启动到结束之间,但是这几个程序在同一个处理机上进行,是会互相抢占资源的。(拓展:工作负载。CPU-Bound/IO-Bound)
并行:在某一个时间点,多个程序同时进行,一个CPU在执行一个进程时,另一个CPU可以执行另一个进程,两个进程在同一时间点同时发生,不会互相抢占资源,可以同时进行。
其次,部分详细内容:
M (包含goroutine的线程)工作过程:
第一步,从工作线程本地运行队列中寻找goroutine;
第二步,从全局运行队列中寻找goroutine。为了保证公平调度,M 需要每经过61次调度就需要优先尝试从全局运行队列中找出一个goroutine来运行。又因为全局运行队列是所有工作线程能够访问的,所以在访问它之前需要加上锁;
第三步,从其它工作线程的运行队列中偷取goroutine。如果上一步没有找到需要运行的goroutine,则需要调用findrunable从其它工作线程的队列中偷取,在进行偷取前,他会再次尝试从全局运行队列和当前运行队列中查找需要运行的goroutine。
其它知识:
栈:栈分为 普通栈 和 线程栈
普通栈:是指 调度的goroutine组成的函数栈,是可增长的
线程栈:是指 由需要将goroutine放置上的 M 组成的,实质上 M 由goroutine生成,此栈大小固定
队列:队列分为 全局队列 和 本地队列
全局队列:
1.队列中的 G 被所有 M 全局共享, 为了保证数据竞争的问题,需要加锁
2.M 自旋状态下会偷取其它队列的一半G,放置在自身的本地队列上,后续也是先从该队列偷取,没有则从其它本地队列偷取
本地队列:
该队列存储数据资源相同的任务,每个本地队列都会绑定一个 M,指定其完成任务,没有数据竞争,无需加锁处理,处理速度远高于全局队列
线程清理:由于每个 P 都需要绑定一个 M 进行任务执行,所以当清理线程的时候,只需要将 P 释放,M 就失去了任务。主要也分两种情况:
主动释放:例子:当执行 G 任务时有系统调用,当发生系统调用的时候 M 会处于阻塞状态。那么调度器会设置一个超时时间,当超过这个时间,会立刻将 P 释放
被动释放:如果发生了系统调用,有一个专门监控程序,扫描当处于阻塞的 P/M,当系统超过了该时间,会自动将 P 资源抢走。