Go调度器中的三种结构G、P、M

Go调度器详解:G、P、M模型与并发调度
Go的并发调度器采用G-P-M模型,其中G表示Goroutine,P代表Processor,M代表Machine。Goroutine是轻量级的并发单元,需要绑定到P才能被调度执行。P维护执行环境和任务队列,数量由GOMAXPROCS决定。M是系统线程,执行schedule循环,从全局或局部队列获取G。P的work-stealing算法提高了调度效率。当G阻塞时,M会解绑P并进入休眠,或尝试窃取其他P的G。Go调度器的设计使得它能处理高并发场景,但也需要注意并发控制以避免性能开销。

 

Go调度器中的三种结构G、P、M

系统线程固定2M,且维护一堆上下文,对需求多变的并发应用并不友好,有可能造成内存浪费或内存不够用。Go将并发的单位下降到线程以下,由其设计的goroutine初始空间非常小,仅2kb,但支持动态扩容到最大1G,这就是go自己的并发单元——goroutine协程。

实际上系统最小的执行单元仍然是线程,go运行时执行的协程也是挂载到某一系统线程之上的,这种协程与系统线程的调度分配由Go的并发调度器承担,Go的并发调度器是属于混合的二级调度并发模型,其内部设计有G、P、M三种抽象结构,我们来看一下它们分别是什么:

G-P-M模型抽象结构:

  • G: 表示Goroutine,每个Goroutine对应一个G结构体,G存储Goroutine的运行堆栈、状态以及任务函数,可重用。G运行队列是一个栈结构,分全局队列和P绑定的局部队列,每个G不能独立运行,它需要绑定到P才能被调度执行。
  • P: Processor,表示逻辑处理器, 对G来说,P相当于CPU核,G只有绑定到P(在P的local runq中)才能被调度。对M来说,P提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等,P的数量决定了系统内最大可并行的G的数量(前提:物理CPU核数 >= P的数量),P的数量由用户设置的GOMAXPROCS决定,但是不论GOMAXPROCS设置为多大,P的数量最大为256。
  • M: Machine,系统物理线程,代表着真正执行计算的资源,在绑定有效的P后,进入schedule循环;而schedule循环的机制大致是从Global队列、P的Local队列以及wait队列中获取G,切换到G的执行栈上并执行G的函数,调用goexit做清理工作并回到M,如此反复。M并不保留G状态,这是G可以跨M调度的基础,M的数量是不定的,由Go Runtime调整,为了防止创建过多OS线程导致系统调度不过来,目前默认最大限制为10000个。

关于P这个设计,是在Go1.0之后才实现的,起初的Go并发性能并不十分亮眼,协程和系统线程的调度比较粗暴,导致很多性能问题

<think>嗯,用户问的是Golang的Goroutine调度器为什么被称为分层调度器,还有分层调度机制的原理。首先,我得回忆一下Golang调度器的发展历程,因为分层调度这个概念可能和它的演进有关。记得早期Go使用的是GM模型,也就是一个全局队列,所有Goroutine都由这个队列分配,这可能会有锁竞争的问题,导致扩展性不好。后来改进成GMP模型,引入了P(Processor),这样每个P都有自己的本地队列,减少了全局锁的争用。这里的P可能就是分层的一部分,因为调度分成了全局层和本地层。分层调度应该是指调度器分为不同层次的结构,比如全局和本地。用户提到的分层调度机制,可能是指调度器在多个层次上分配和管理Goroutine,比如全局队列、P的本地队列,还有偷取机制。这样的话,调度任务先在本地处理,不够的时候再从全局或其他P偷取,这样层次分明,效率更高。另外,用户提供的引用里提到GMP模型和调度机制,比如引用1说GMP模型不断演进,引用5提到STW后恢复Goroutines。这可能涉及到调度器的层次结构如何在不同阶段处理任务,比如在垃圾回收时暂停和恢复,这也属于调度的一部分。需要解释清楚分层指的是哪几层,每层的作用,比如全局层负责平衡,本地层负责快速执行,偷取层处理负载均衡。还要说明为什么这种分层设计能提高效率,比如减少锁竞争、利用多核优势等。可能还要对比传统的线程调度,说明Golang的分层如何更高效。 用户可能还想知道分层带来的好处,比如扩展性、低延迟,这些都需要在回答中覆盖。另外,引用中提到的Kubernetes控制器使用Goroutine处理事件,这也展示了分层调度在实际中的应用,比如高效处理大量并发任务。 要确保回答结构清晰,分点解释分层结构,然后说明原理,最后总结优势。可能还需要相关的问题,比如和传统线程调度的区别,GMP模型细节,或者调度中的工作窃取机制,帮助用户深入理解。</think>### Golang Goroutine 调度器的分层调度机制原理 Golang 的 Goroutine 调度器被称为**分层调度器**,核心在于其通过多级调度结构实现高效的任务分配与执行。以下从分层机制和原理两方面展开: --- #### 一、分层调度机制的结构 Golang 的调度器模型(GMP)分为三层: 1. **全局层(Global Layer)** - 负责管理全局队列(Global Run Queue)和空闲的处理器(P)。 - 当本地队列(Local Run Queue)不足时,从全局队列中获取 Goroutine(G)进行调度。 - 维护全局的负载均衡,例如在多个处理器(P)之间分配任务。 2. **本地层(Local Layer)** - 每个处理器(P)绑定一个本地队列,用于存储待执行的 Goroutine。 - 每个 P 直接调度其本地队列中的 G,避免全局锁竞争,提高并发效率[^1]。 - 若本地队列为空,则触发**工作窃取(Work Stealing)**,从其他 P 的本地队列或全局队列中偷取 G。 3. **系统层(System Layer)** - 由操作系统线程(M)直接执行 Goroutine。 - M 与 P 绑定,当 G 发生系统调用阻塞时,M 会释放 P 以让其他 M 继续执行其他 G,避免资源浪费[^5]。 --- #### 二、分层调度的核心原理 1. **减少锁竞争** - 本地队列的设计避免了全局队列的锁竞争,每个 P 独立处理任务,适合多核 CPU 的并行场景[^1]。 - 例如,当多个 Goroutine 被创建时,优先分配到各自的本地队列,而非全局队列。 2. **动态负载均衡** - **工作窃取算法**:当某个 P 的本地队列为空时,会从其他 P 的队列中窃取 G,确保 CPU 资源充分利用[^1]。 - **全局队列兜底**:当本地队列和窃取均无法获取任务时,从全局队列拉取 G。 3. **非对称协作** - 调度器与操作系统线程(M)解耦,通过 P 的抽象层管理资源。例如,M 的数量可动态调整,而 P 的数量通常等于 CPU 核数[^5]。 - 当 G 阻塞时(如 I/O 操作),M 会与 P 解绑,P 可被其他 M 复用,避免线程资源浪费[^2]。 --- #### 三、分层调度的优势 1. **高并发性能**:通过本地队列和窃取机制,减少锁竞争,提升吞吐量。 2. **低延迟**:优先调度本地队列任务,缩短任务等待时间。 3. **资源高效利用**:解耦 M 和 P,避免线程频繁创建/销毁的开销[^5]。 --- ### 示例代码:Goroutine 调度示意 ```go func main() { // 启动多个 Goroutine,由调度器自动分配到不同 P 的本地队列 for i := 0; i < 1000; i++ { go func(id int) { fmt.Println("Goroutine", id) }(i) } time.Sleep(time.Second) } ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值