初始化
调度器的初始化从 schedinit()函数开始,将会设置m最大个数(maxmcount)及p最大个数(GOMAXPROCS)等
func schedinit() {
sched.maxmcount = 10000 // 设置m的最大值为10000
mcommoninit(_g_.m) //初始化当前m
// 确认P的个数
// 默认等于cpu个数,可以通过GOMAXPROCS环境变量更改
procs := ncpu
if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
procs = n
}
// 调整P的个数,这里是新分配procs个P
// 这个函数很重要,所有的P都是从这里分配的,以后也不用担心没有P了
if procresize(procs) != nil {
throw("unknown runnable goroutine during bootstrap")
}
...
}
procresize方法主要完成以下任务:
- 比较目标个数和原始p的个数,进行全局缓存的扩容或收缩
- 遍历p的缓存,将未初始化的p进行初始化
- 对于收缩的情况,将收缩的p进行回收处理
- 分别将空闲的p和有任务的p加入空闲链表和工作链表
下面是procresize()的源码:
//全局数据结构:
allp []*p // len(allp) == gomaxprocs; may change at safe points, otherwise immutable
sched schedt //全局调度器(综述文中有介绍)
// 所有的P都在这个函数分配,不管是最开始的初始化分配,还是后期调整
func procresize(nprocs int32) *p {
...
old := gomaxprocs
// 扩张allp数组
if nprocs > int32(len(allp)) {
lock(&allpLock)
if nprocs <= int32(cap(allp)) {
allp = allp[:nprocs]
} else {
// 分配nprocs个*p
nallp := make([]*p, nprocs)
copy(nallp, allp[:cap(allp)])
allp = nallp
}
unlock(&allpLock)
}
// 初始化新的p
for i := int32(0); i < nprocs; i++ {
pp := allp[i]
if pp == nil {
pp = new(p)
...
// 将pp保存到allp数组里, allp[i] = pp
atomicstorep(unsafe.Pointer(&allp[i]), unsafe.Pointer(pp))
}
...
}
// 释放无用的p
for i := nprocs; i < old; i++ {
p := allp[i]
// 任务转移
// 本地任务队列转换到全局队列
for p.runqhead != p.runqtail {
p.runqtail--
gp := p.runq[p.runqtail%uint32(len(p.runq))].ptr()
globrunqputhead(gp)
}
// 优先执行的也转移到全局
if p.runnext != 0 {
globrunqputhead(p.runnext.ptr())
p.runnext = 0
}
// 后台标记的g也转移
if gp := p.gcBgMarkWorker.ptr(); gp != nil {
casgstatus(gp, _Gwaiting, _Grunnable)
globrunqput(gp)
p.gcBgMarkWorker.set(nil)
}
// 做一些内存释放等操作
...
}
...
//将p放入队列
var runnablePs *p
for i := nprocs - 1; i >= 0; i-- {
p := allp[i]
// 如果是当前的M绑定的P,不放入P空闲链表
// 否则更改P的状态为_Pidle,放入P空闲链表
if _g_.m.p.ptr() == p {
continue
}
p.status = _Pidle
if runqempty(p) {
pidleput(p)// 将空闲p放入全局空闲链表
} else {
// 非空闲的通过绑定m,链起来
p.m.set(mget())
p.link.set(runnablePs)
// 最后一个空闲的不加入空闲列表 直接返回去调度使用
runnablePs = p
}
}
}
新建的无任务p都会被放到空闲链表中:
func pidleput(_p_ *p) {
if !runqempty(_p_) {
throw("pidleput: P has non-empty run queue")
}
_p_.link = sched.pidle //通过p的link形成链表
sched.pidle.set(_p_)
// 将sched.npidle加1
atomic.Xadd(&sched.npidle, 1) // TODO: fast atomic
}
默认只有schedinit和startTheWorld会调用procresize()schedinit初始化p,startTheWorld会激活所有有任务的p。
完成调度器初始化后,系统会引导生成 main goroutine,之前是在全局的g0上执行初始化工作
golang支持在运行间修改p数量:runtime.GOMAXPROCS(),但是带价很大,会触发STW
lock(&sched.lock)
ret := int(gomaxprocs)
unlock(&sched.lock)
if n <= 0 || n == ret {
return ret
}
// 有stw和重启世界的过程