golang调度学习-初始化,创建goroutine,系统线程m

初始化调度器的初始化从 schedinit()函数开始,将会设置m最大个数(maxmcount)及p最大个数(GOMAXPROCS)等func schedinit() { sched.maxmcount = 10000 // 设置m的最大值为10000 mcommoninit(_g_.m) //初始化当前m // 确认P的个数 // 默认等于cpu个数,可以通...
摘要由CSDN通过智能技术生成

初始化

调度器的初始化从 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方法主要完成以下任务:

  1. 比较目标个数和原始p的个数,进行全局缓存的扩容或收缩
  2. 遍历p的缓存,将未初始化的p进行初始化
  3. 对于收缩的情况,将收缩的p进行回收处理
  4. 分别将空闲的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和重启世界的过程
    
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值