NOTE

3.1 GMP

1. goroutine调度器 - Golang中协程的调度模型,即有M个线程,N个协程,该怎么分配协程给线程执行 - 这个本质上和操作系统的调度器类似,即有M个处理器,N个线程,怎么分配处理器给线程执行 1.1. GM模型 - 最开始采用的是GM模型 - G:goroutine,M:内核线程 -

Go创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. goroutine调度器

  • Golang中协程的调度模型,即有M个线程,N个协程,该怎么分配协程给线程执行
  • 这个本质上和操作系统的调度器类似,即有M个处理器,N个线程,怎么分配处理器给线程执行

1.1. GM模型

  • 最开始采用的是GM模型
    • G:goroutine,M:内核线程
    • 这个模型中,所有的G放在全局队列中,M从全局队列中取出和放回G需要加锁
    • 问题:
      • 加锁效率低
      • 局部性差

1.2. GMP模型

  • 为了解决GM模型的缺点,推出了GMP模型。M绑定到P,P有个本地队列存放着goroutine,M从中取出goroutine执行;如果P的本地队列为空,那么从全局队列或者其他P偷取
  • 一句话:m 需要获得 p 才能运行 g

1.2.1. P

  • Processor,抽象的处理器。
  • GOMAXPROCS环境变量或者runtime.GOMAXPROCS()设置的是会有多少个操作系统的线程同时执行Go的代码,即GMP中P的数量。

1.2.2. M

  • Machine,内核线程。
  • 数量由go语言设置,默认为1W个,可以通过runtime/debug的SetMaxThreads设置

2026 注:这里的“1W”是旧理解。SetMaxThreads 默认的 10000 是操作系统线程数量上限,不是 runtime 默认创建 1 万个 M。

  • 如果有一个M阻塞,那么会创建新的M
  • 如果有M空闲,那么这个M会被回收或者睡眠

1.2.3. G

  • Goroutine,协程。
  • 执行用户代码
  • runtime.NumGoroutine()获取当前总的协程数量,即GMP中的G
  • 也可以通过pprof.md来获取G的数量

2. 调度器创建goroutine流程

2.1. 初始化M0和G0

  • M0
    • 启动程序后的编号为0的主线程
    • 负责执行初始化操作和启动第一个G
  • G0
    • 每次启动一个新的M,都会创建一个G,这个第一个创建的G就是G0
    • 作用
      • 用于调度在M上执行的其他goroutine
      • 用于创建其他goroutine
      • 用于执行gc
      • 用于栈扩容

2.2. go func执行流程

3. 抢占式调度

最开始协程由用户态调度是协作式的,一个协程让出CPU后,才执行下一个协程;后来为了防止一个goroutine运行时间过长,改成抢占式 抢占时机:

  • 只有长时间阻塞于系统调用,或者运行了较长时间才会被抢占
  • runtime会在后台有一个检测线程,它会检测这些情况,并通知goroutine执行调度

2026 注:Go 1.14 起支持异步抢占,因此上面的抢占时机不再能完整描述当前 runtime。

4. 参考