go调度器-startm

今天聊聊startm函数,字面意思理解,新增或者从M的空闲队列中取出M并“开始”,“开始”在这里是个模糊的意思,需要进行精确的定义

作者认为单独启动M是没有意义的,一定要配上P一起,才能进行G的处理(我自己设计可能不会考虑到作为整体启动),按照这个思路就可以理解作者传入参数_p_的目的

  • _p_==nil,从空闲p队列获取
  • _p_!=nil,就使用_p_M关联

参数spinning==true代表调用方已经增加了nmspinning(空闲M的数量),startm中有可能减少nmspinning或者让新分配的M处于spinning状态

阻止startm使用idle M的代码如下:

if mp.spinning { // 从midle中获取的mp,不应该是spinning状态,获取的都是经过stopm的,stopm之前都会推出spinning
	throw("startm: m is spinning")
}
if mp.nextp != 0 { // 这个位置是要留给参数_p_的,stopm中如果被唤醒,则关联nextp和m
	throw("startm: m has p")
}
if spinning && !runqempty(_p_) { // spinning状态的M是在本地和全局都获取不到工作的情况,不能与spinning语义矛盾
	throw("startm: p has runnable gs")
}

方法结尾调用notewakeup,唤醒当前m,sleep的位置是在stopm中。

当p中的runq或者sched中的runq中都包含需要执行的g,此时得到的空闲m,不能赋值spinning为true,因为是要马上接受任务并处理的。

而如果当前处于增加资源用于防止未来资源紧张的情况下,允许新增spinning状态的mp。

spinning我的理解是中间状态,m发现本地和全局g队列都为空,进入spinning状态,如果偷不到g,退出spinning,进入stop状态,等待notewakeup。

下面说明没有空闲M的情况,会调用newm,分几块说明newm

《go并发编程事件》作者提过关于allocm问题,帖子中的问题分几个

  • allocm中,先acquirep,然后releasep,借用传入的_p_,这么做的原因?

p中有mcache,这时当前g中申请内存的基础,所以如果在newm过程中发现,当前g缺少p,可以临时借用传入的p。这个p会在allocm之后和新的mp关联。

  • m中的g0/m0作用?

每个m都含有g0,管理协程,负责调度,gc,stack管理等。通常,m会按照g1->g0->g2的顺序执行。g0发挥承上启下的作用。

此外,g0对应OS thread的stack。Windows不需要申请,所以g0=malg(-1).

  • runtime的g0/m0作用?

启动用的,他们是第一个线程,静态分配,因为我们还没有内存分配器。

接下来,newosproc

这里涉及核心的方法是clone,是系统调用

  • cloneFlags控制clone运作方式,主要是父process和子process是否共享某些资源
  • stk子栈空间地址,父子共享内存,所以子需要新的空间
  • mp是父process(即m)的地址
  • gp是子process(g0和m一起诞生)的地址
  • fn,从语境推测是hook方法,用来做些首尾工作
go调度器-startm
Share this