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 的时候,才会新建