Golang 中 GPM 之 G 从哪里来

1,195 阅读2分钟

解释下我探究的思路,我的 Golang 版本是 1.14.4 ,如下,是我们这次实验的代码

package main
​
import "fmt"func main() {
  go func() {
    fmt.Println()
  }()
}

以上的代码中通过 go 关键字生成了协程,我们可以反编译找到具体的函数 

go tool compile -S -N -l main.go
//可以通过 go tool compile 查询 具体的参数的意思
// -S 是打印编译列表
// -N 是禁止编译器优化
// -l 是禁止内联

我们可以得到 

因此我们跳转到 runtime.newproc(SB) 

func newproc(siz int32, fn *funcval) {
  argp := add(unsafe.Pointer(&fn), sys.PtrSize)
  gp := getg() // 这个是获取当前 g
  pc := getcallerpc()
  systemstack(func() {
    newproc1(fn, argp, siz, gp, pc) // 这里是真正的创建一个 go 协程
  })
}

func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) {
  _g_ := getg() //获取当前 G 的指针
​
  _p_ := _g_.m.p.ptr() // 获取当前 G 对应的 P 的指针
  newg := gfget(_p_)// 查询是否可以复用的 G ,可以看下文
  if newg == nil { //如果找不到,则创建一个新 G
    newg = malg(_StackMin) // 这里就是创建新 G 的核心部分
    ...
  }  ...
}

// 先查询是否有可以复用的 G 
func gfget(_p_ *p) *g {
retry:
  // 首先 _p_.gFree 表示其本地的可用的 G ,sched.gFree 表示全局可用的 G
  if _p_.gFree.empty() && (!sched.gFree.stack.empty() || !sched.gFree.noStack.empty()) {
    lock(&sched.gFree.lock)
    // Move a batch of free Gs to the P.
    for _p_.gFree.n < 32 { // 如果本地可用的 G 的数量小于 32 ,则将全局中的 G 加入到本地 P 中 ,直到本地 P 数量到达 32 或者 全局 G 无可用的
      // 优先选择存在栈的 G
      gp := sched.gFree.stack.pop()
      if gp == nil {
        gp = sched.gFree.noStack.pop()
        if gp == nil {
          break
        }
      }
      sched.gFree.n--
      _p_.gFree.push(gp)
      _p_.gFree.n++
    }
    unlock(&sched.gFree.lock)
    goto retry
  }
  gp := _p_.gFree.pop() 
  if gp == nil {
    return nil //如果都没有可用的 G ,则返回nil
  }
  ...
  return gp
}

总结: Golang 中的 G 是可以复用的,来源分别是本地 P 和全局 G ,而且当两者都没有可用的 G 的时候,才会新建

如果觉得有什么不对或者不好的地方欢迎关注我的公众号进行留言,希望能一起学习一起进步,谢谢各位大佬