1. package main
    2. import "fmt"
    3. func main() {
    4. go func() {
    5. fmt.Println()
    6. }()
    7. }

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

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

    我们可以得到
    Golang 中 GPM 之 G 从哪里来 - 图1
    因此我们跳转到 runtime.newproc(SB)

    1. func newproc(siz int32, fn *funcval) {
    2. argp := add(unsafe.Pointer(&fn), sys.PtrSize)
    3. gp := getg() // 这个是获取当前 g
    4. pc := getcallerpc()
    5. systemstack(func() {
    6. newproc1(fn, argp, siz, gp, pc) // 这里是真正的创建一个 go 协程
    7. })
    8. }
    9. func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) {
    10. _g_ := getg() //获取当前 G 的指针
    11. _p_ := _g_.m.p.ptr() // 获取当前 G 对应的 P 的指针
    12. newg := gfget(_p_)// 查询是否可以复用的 G ,可以看下文
    13. if newg == nil { //如果找不到,则创建一个新 G
    14. newg = malg(_StackMin) // 这里就是创建新 G 的核心部分
    15. ...
    16. } ...
    17. }
    18. // 先查询是否有可以复用的 G
    19. func gfget(_p_ *p) *g {
    20. retry:
    21. // 首先 _p_.gFree 表示其本地的可用的 G ,sched.gFree 表示全局可用的 G
    22. if _p_.gFree.empty() && (!sched.gFree.stack.empty() || !sched.gFree.noStack.empty()) {
    23. lock(&sched.gFree.lock)
    24. // Move a batch of free Gs to the P.
    25. for _p_.gFree.n < 32 { // 如果本地可用的 G 的数量小于 32 ,则将全局中的 G 加入到本地 P 中 ,直到本地 P 数量到达 32 或者 全局 G 无可用的
    26. // 优先选择存在栈的 G
    27. gp := sched.gFree.stack.pop()
    28. if gp == nil {
    29. gp = sched.gFree.noStack.pop()
    30. if gp == nil {
    31. break
    32. }
    33. }
    34. sched.gFree.n--
    35. _p_.gFree.push(gp)
    36. _p_.gFree.n++
    37. }
    38. unlock(&sched.gFree.lock)
    39. goto retry
    40. }
    41. gp := _p_.gFree.pop()
    42. if gp == nil {
    43. return nil //如果都没有可用的 G ,则返回nil
    44. }
    45. ...
    46. return gp
    47. }

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