深入理解Go语言
上QQ阅读APP看书,第一时间看更新

1.2.1 GPM模型

在Go中,线程是运行Goroutine的实体,调度器的功能是把可运行的Goroutine分配到工作线程上。

在GPM模型中有以下几个重要的概念,如图1.12所示。

图1.12 GPM模型

(1)全局队列(Global Queue):存放等待运行的G。全局队列可能被任意的P去获取里面的G,所以全局队列相当于整个模型中的全局资源,那么自然对于队列的读写操作是要加入互斥动作的。

(2)P的本地队列:同全局队列类似,存放的也是等待运行的G,但存放的数量有限,不超过256个。新建G′时,G′优先加入P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列。

(3)P列表:所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个。

(4)M:线程想运行任务就得获取P,从P的本地队列获取G,当P队列为空时,M也会尝试从全局队列获得一批G放到P的本地队列,或从其他P的本地队列“偷”一半放到自己P的本地队列。M运行G,G执行之后,M会从P获取下一个G,不断重复下去。

Goroutine调度器和OS调度器是通过M结合起来的,每个M都代表了1个内核线程,OS调度器负责把内核线程分配到CPU的核上执行。

1.有关P和M个数的问题

(1)P的数量由启动时环境变量$GOMAXPROCS或者由runtime的方法GOMAXPROCS()决定。这意味着在程序执行的任意时刻都只有$GOMAXPROCS个Goroutine在同时运行。

(2)M的数量由Go语言本身的限制决定,Go程序启动时会设置M的最大数量,默认为10 000个,但是内核很难支持这么多的线程数,所以这个限制可以忽略。runtime/deBug中的SetMaxThreads()函数可设置M的最大数量,当一个M阻塞了时会创建新的M。

M与P的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来。

2.有关P和M何时被创建

(1)P创建的时机在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P。

(2)M创建的时机是在当没有足够的M来关联P并运行其中可运行的G的时候。例如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,如果此时没有空闲的M,就会去创建新的M。