基本GMP模型内容

  • 全局G队列:等待运行的G。加入读取都需要加锁。
  • 本地G队列:每个P都会有一个。该队列最大存储256个。
  • P:可用通过$GOMAXPROCS环境变量和runtime.``GOMAXPROCS设置
  • M:最大1000个,理论值实际不可能达到。

调度器的设计

  1. 复用线程:work stealing, hand off。本地队列 -> 全局队列 -> 偷取一半3
  2. 利用并行:$GOMAXPROCS
  3. 抢占式:每一个G最多运行10ms,会起一个单独的M运行sysmon监控G的执行时间,不是直接抢占,而是对G进行标记(1.14)
  4. 全局G:

go 指令调度

会优先放入当前G的P的本地队列,满了才会放到全局G队列中

M0

  • 编号为0的系统级线程,放到全局变量runtime.m0中,主要负责启动第一个G0
  • 启动完第一个G0,M0就和其他的M一样。该G0主要负责初始化创建多个P,然后调度运行main.main
  • 在创建完main.main并放入一个P,就销毁了

G0

  • 每次启动一个M都会创建一个goroutine,就是G0
  • G0负责调度G,G0不会执行任何函数
  • 在调度或者系统调用的时候,M会用G0来调度,G0放在一个单独的全局空间

几个关键字

  • gomaxprocs:P的数目
  • idleprocs:处于刚分配还未初始化的的P (gomaxprocs - idleprocs = 目前在处理的P)
  • spinningthreads:自旋线程
  • idlethreads:
  • runqueue

唤醒 和 自旋

自旋

  • 从M休眠队列 唤醒一个g0,g0自旋,然后会从全局拿G,拿的过程会有一个负载均衡。全局没有回去别的P的本地队列偷一半过来。
  • 自旋线程 + 执行线程 <= GOMAXPROCS。对于的线程M都会在休眠队列
  • 自旋线程是抢占G的

唤醒

  • syscall/阻塞:G和M绑定,P去寻找其他休眠的M 或 创建一个新的M 或 进入P的休眠队列。
  • 阻塞 -> 非阻塞 / syscall 完成:M 会先去抢原来的P -> 去P空闲队列找 -> 进入M的休眠队列。 G会被放入全局队列中。

M休眠队列长期休眠就会销毁了