大家好,我是渔夫子。
今天给大家介绍一下Go协程调度器的G-M-P的模型,以及一个线程在该模型下是如何被调度的。
在现代操作系统中,分配资源的基本单位是进程。而在进程中,独立运行和调度的基本单位是线程。但是,在Go语言中,执行和调度的基本单位是协程。为了更高效的执行协程,Go语言实现了自己的协程调度模型。
我们通过下面的一段简单代码来帮助你更好的理解Go程序中的协程。
for i := 0; i < 4; i++ {
go func() {
time.Sleep(time.Second)
}()
}
fmt.Println(runtime.NumGoroutine())
上述代码最终输出结果是 5。也就是说在这段代码中启动了5个协程。但这里的问题是,这5个协程并不能直接向操作系统申请运行资源(CPU、内存等),而是必须要被分配给内核态下的线程才能执行,也就是说要依赖于操作系统级别的线程调度模型才行。根据用户级线程和内核级线程之间的对应关系,有三种模型:1对1、N对1和M对N。以上5个goroutine是如何分布在内核级线程上的?这是由Go的goroutine调度程序决定的。
在Go语言中,协程调度器是基于G-M-P模型实现的。
runtime.GOMAXPROCS
设定。下面是一个1个M只绑定1个P的关系模型。如下图:
在代码中,当通过代码 go func(){}
启动一个协程后,GMP是如何工作的呢?下图详细解释了GMP是如何调度协程的。
func(){}
函数。对于goroutine调度器来说最重要的调度策略是:重用,避免频繁的资源创建和破坏,最大限度地提高系统吞吐量和并发性。这是操作系统进行线程调度的最终目标。重用也是许多“池技术”的基础。围绕这一原则,goroutine调度器通过以下方式优化调度策略:
此外,在go的1.14版本中,go语言技术团队试图向调度器中添加可抢占技术,具体可参考:https://github.com/golang/go/issues/24543
在Go语言早期,协程调度器模型并不是G-M-P,而是G-M模型。整个调度器就只有一个全局的等待队列G,同时所有的M都从全局的队列中获取协程G来执行。该模型最初应用于go1.1版本,后来被现在的G-M-P模型给替代,即加入了协程的本地队列。增加P的原因如下:
本文通过一个简单的协程代码,来说明协程是如何在G-M-P模型下执行的。同时还回顾了最早的G-M模型,通过分析G-M模型的缺点,说明引入P的优越性。
原文地址:https://www.sobyte.net/post/2023-03/gpm/#goroutine-tour
别说明:你的关注,是我写下去的最大动力。点击下方公众号卡片,直接关注。关注送《100个go常见的错误》pdf文档、经典go学习资料。