• runtime/proc.go, runtime/runtime2.go

G

  • G (Goroutine / Coroutine) 对协程的抽象;

  • 有自己的运行栈、生命周期状态、以及执行的任务函数(用户通过 go func 指定);

  • 需要绑定在 m 上执行

  • 承载 G 的容器有两个

    • p 的本地队列 lrq(local run queue):这是每个 p 私有的 g 队列,通常由 p 自行访问,并发竞争情况较少,因此设计为无锁化结构,通过 CAS(compare-and-swap)操作访问
    • 全局队列 grq(global run queue):是全局调度模块 schedt 中的全局共享 g 队列,作为当某个 lrq 不满足条件时的备用容器,因为不同的 p 都可能访问 grq,因此并发竞争比较激烈,访问前需要加全局锁
    • put g:当某个 g 中通过 go func(){...} 操作创建子 g 时,会先尝试将子 g 添加到当前所在 p 的 lrq 中(无锁化);如果 lrq 满了,则会将 g 追加到 grq 中(全局锁)
    • get g:gmp 调度流程中,m 和 p 结合后,运行的 g0 会不断寻找合适的 g 用于执行,此时会采取“负载均衡”的思路,遵循如下实施步骤:
      • 优先从当前 p 的 lrq 中获取 g(无锁化-CAS)
      • 从全局的 grq 中获取 g(全局锁)
      • 取 io 就绪的 g(netpoll 机制)
      • 从其他 p 的 lrq 中窃取 g(无锁化-CAS)

M

  • M(Thread) 内核线程,对线程的抽象;
  • m 需要和 p 进行结合,从而进入到 gmp 调度体系之中
  • m 的运行目标始终在 g0 和 g 之间进行切换——当运行 g0 时执行的是 m 的调度流程,负责寻找合适的“任务”,也就是 g;当运行 g 时,执行的是 m 获取到的”任务“,也就是用户通过 go func 启动的 goroutine

P

  • P(Processor) 进程,调度器;
  • p 可以理解为 m 的执行代理,m 需要与 p 绑定后,才会进入到 gmp 调度模式当中;因此 p 的数量决定了 g 最大并行数量
  • p 是 g 的存储容器,其自带一个本地 g 队列(local run queue,简称 lrq),承载着一系列等待被调度的 g