文章

GMP 线程调度模型

GMP 线程调度模型

GMP线程调度模型是Go协程调度的CSP并发模型实现,是对两级线程模型进行了一定程度的改进,使它能够更加灵活地进行线程之间的调度。

Golang内部有三个对象,goroutinemachineprocessor

研究这块最好结合源码。

GMP 调度模型

Desktop View GMP 调度模型

G=Goroutine协程,P=Processor处理器,M=Thread线程

  • 全局队列(Global Queue):存放等待运行的G
  • P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G’时,G’优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列。
  • P:所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置) 个。
  • M:线程想运行任务就得获取P,从P的本地队列获取GP队列为空时,M也会尝试从全局队列拿一批G放到P的本地队列,或从其他P的本地队列偷一半放到自己P的本地队列。M运行GG执行之后,M会从P获取下一个G,不断重复下去。

说明:

  • GGoroutine,协程,轻量级的用户线程,是对Go语言中代码片段的封装;
  • MMachine,物理线程(工作线程),对内核级线程的封装,一个Machine对应一个内核线程,当前操作系统分配到当前Go程序的线程数,由于Go语言本身的限制,Go程序启动时会设置M的最大数量默认为10000,但是内核很难支持这么多的线程数,所以这个限制可以忽略。可以通过runtime/debug中的SetMaxThreads函数来设置M的最大数量;
  • PProcessor,处理器(上下文),GM的调度对象,用来调度GM之间的关联关系,程序启动时创建,其数量可通过GOMAXPROCS()来设置,默认为CPU核心数,这意味着在程序执行的任意时刻都只有GOMAXPROCSgoroutine在同时运行。

Goroutine调度器和OS调度器是通过M结合起来的,每个M都会与1个内核线程绑定,OS调度器负责把内核线程分配到CPU的核上执行。在运行时一个M同时只能绑定1PMP是一对一绑定的,MP的组合共同构成了G的有效运行环境。但MP会适时的组合和断开,以保证待执行G队列能够得到及时执行。而PG的关系是一对多的,多个可执行G将会顺序排成一个队列挂在某个P上面。在运行过程中,M和内核线程之间的对应关系不会变,在M的生命周期内,它只会和一个内核线程绑定,而MP以及PG之间的关系都是动态可变的。

MP的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来。但由于P的存在,GM可以呈现出多对多的关系。当一个正在与某个M对接并运行着的G,需要因某个事件(比如等待I/O或锁的解除)而暂停运行的时候,调度器总会及时地发现,并把这个G与那个M分离开,以释放计算资源供那些等待运行的G使用。

PM何时会被创建

  • P何时创建:在确定了P的最大数量n后,运行时系统会根据这个数量创建nP
  • M何时创建:没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M

go func() 调度流程

Desktop View go func() 调度流程

  1. 我们通过go func ()来创建一个goroutine
  2. 有两个存储G的队列,一个是局部调度器P的本地队列、一个是全局G队列。新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列中;
  3. G只能运行在M中,一个M必须持有一个PMP1:1的关系。M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会想其他的MP组合偷取一个可执行的G来执行;
  4. 一个M调度G执行的过程是一个循环机制;
  5. M执行某一个G时候如果发生了syscall或则其余阻塞操作,M会阻塞,如果当前有一些G在执行,runtime会把这个线程MP中摘除 (detach),然后再创建一个新的操作系统的线程 (如果有空闲的线程可用就复用空闲线程) 来服务于这个P
  6. M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中。

调度器的生命周期

Desktop View 调度器的生命周期

本文由作者按照 CC BY 4.0 进行授权

© ManShouyuan. 保留部分权利。

本站总访问量 本站访客数人次

🚩🚩🚩🚩🚩🚩