MST

星途 面试题库

面试题:Go协程的调度器原理

深入讲解Go语言协程调度器(Goroutine Scheduler)的工作原理,如M:N调度模型,G、M、P的含义及它们之间是如何协同工作来实现高效的协程调度的。
10.5万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

Go语言协程调度器工作原理

M:N调度模型

在传统的线程模型中,有1:1(一个用户线程映射到一个内核线程)和N:1(多个用户线程映射到一个内核线程)模型。Go语言采用的M:N调度模型,是指M个用户级线程(协程,即Goroutine)映射到N个内核线程。这种模型结合了1:1和N:1模型的优点,既能利用多核CPU,又能在用户态高效地管理和调度大量的轻量级线程(协程)。

G、M、P的含义

  1. G(Goroutine):代表Go语言中的协程,是用户级的轻量级线程。每个G都有自己的栈空间、程序计数器、局部变量等运行状态。它是Go语言并发编程的基本执行单元,创建和销毁的开销极小,可以轻松创建成千上万的Goroutine。
  2. M(Machine):代表操作系统的内核线程,一个M对应一个内核线程。M负责执行G,它从P的本地运行队列或者全局运行队列中获取G并执行。M在运行G的过程中,如果遇到系统调用等阻塞操作,会阻塞自身,直到阻塞操作完成。
  3. P(Processor):可以理解为逻辑处理器,它包含了运行G所需的资源,如本地G队列、调度器缓存等。P的数量决定了同一时刻可以并发执行的G的最大数量,默认情况下,P的数量等于CPU的核心数。P的主要作用是管理和调度G,为M提供可运行的G。

G、M、P协同工作过程

  1. G的创建与分配:当创建一个新的Goroutine(G)时,它首先被放入到创建它的P的本地运行队列中。如果本地运行队列已满,G会被放入全局运行队列。
  2. M获取G并执行:M会尝试从某个P的本地运行队列中获取一个G来执行。如果P的本地运行队列为空,M会尝试从全局运行队列中获取G,或者从其他P的本地运行队列中窃取一半的G(工作窃取算法)。一旦M获取到G,就开始执行G的代码。
  3. G的调度切换:在G执行过程中,如果G发生阻塞(如进行系统调用、channel操作等),M会将G与自身分离,将G放入阻塞队列,然后M可以去执行其他的G(如果P的运行队列中有其他可运行的G),或者睡眠等待。当阻塞的G恢复可运行状态时,它会被重新放入到某个P的本地运行队列中。
  4. P的作用:P负责管理和调度G,保证M总有可运行的G。P还会在M因为阻塞等原因无法执行G时,及时将其他可运行的G交给其他空闲的M执行,从而实现高效的并发调度。同时,P的存在使得Go语言的调度器可以在用户态进行高效的调度,减少了内核态与用户态之间的切换开销。

通过G、M、P之间的协同工作,Go语言的协程调度器实现了高效的M:N调度模型,能够在多核环境下充分利用CPU资源,同时支持大量轻量级协程的并发执行,为Go语言的高性能并发编程提供了有力的支持。