14-go调度器

22 篇文章 1 订阅
3 篇文章 0 订阅

引入调度器原因

1. go垃圾回收

垃圾回收需要内存处于一致性状态,需要stop the world,时间点不确定,仅OS调度无法控制。

2. 提高并发性

采用M:N线程模型,每个用户线程对应多个内核空间线程,同时也可以一个内核空间线程对应多个用户空间线程。当一个Goroutine在进行阻塞操作(比如系统调用)时,可以把当前线程中的其他Goroutine移交到其他线程中继续执行, 从而避免了整个程序的阻塞。

3. 更好的管理goroutine

因为引入Goroutine,一个Goroutine既包含要执行的代码,又包含用于执行该代码的栈和PC、SP指针等,对于Goroutine的管理也极为复杂。

4. 栈管理

每个Goroutine都有自己的栈,每个栈大小都不同。如果采用常规方式,以最差情况为每个栈分配足够大的栈空间,会造成内存的极大浪费。为了解决这种问题,gcc引入了Split Stacks技术。创建栈时,只分配一块比较小的内存,如果进行某次函数调用导致栈空间不足时,就会在其他地方分配一块新的栈空间。新的空间不需要和老的栈空间连续。函数调用的参数会拷贝到新的栈空间中,接下来的函数执行都在新栈空间中进行。

4.1 Split Stacks存在问题:

1-hot split拆分热点
如果堆栈几乎满了,调用将强制分配新的堆栈块。当该调用返回时,将释放新的堆栈块。如果相同的调用在紧密循环中重复发生,则alloc/free的开销会导致很大的开销。
2-栈的伸缩不能一次完成
allocation/deallocation工作永无止境,每次堆栈大小在任一方向通过阈值时,都需要额外的工作。
Goroutine为了解决Splie Stacks的效率问题,使用了连续栈技术(Contiguous stacks)。

  1. 所有G一开始默认分配最小栈,在G结束时回收栈,在新的G开始时,从回收的栈里获取一个。即栈是可以重复利用的,减少了内存的申请释放。
  2. 并且Go会记录每个goroutine的栈大小,在下次分配时预先分配足够大的栈。
  3. go没有每次都计算新的栈大小,只是简单的以指数级方式增长栈大小,一次不行则重复,直到栈大小足够。
5. goroutine抢占

Goroutine调度不像OS线程调度那样有时间片的概念,因此实际抢占机制要弱很多:Go中的抢占实际上是为G设置抢占标记(g.stackguard0=stackPreempt),在后续可以触发调度的时候进行判断。

1. 调度器组成

调度器采用MPG,以及schedt来保存调度器所有状态信息。
go调度器主要有4个重要结构:m,p,g,schedt
定义在src\runtime\runtime2.go中。

1.1 M:代表操作系统线程

一个M就是一个线程,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等信息。

m的最大个数为p的个数。

M的状态:
  1. spinning bool; M处于自旋中,M当前没有执行代码,拥有P,并等待获取一个G。
1.2 P: M处理G时的上下文。

Processor,它也维护了一个goroutine队列,存储所有需要它来执行的goroutine。一个P最多256个goroutine。

P的数量默认为系统CPU个数。由环境变量GOMAXPROCS,或者runtime.GOMAXPROCS()设置。

P与M的关系不固定,当P空闲时,就与M解绑。

有两个设置p个数的触发场景:

  1. 在进程刚启动时,由schedinit()调用procresize()创建所有的p。
  2. 运行时,通过runtime.GOMAXPROCS()调用procresize()
P的状态:
  1. _Pidle:当M发现无待运行的G时会进入休眠, 这时M拥有的P会变为空闲并加到空闲P链表中。
  2. _Prunning:当M拥有了一个P后, 这个P的状态就会变为运行中, M运行G会使用这个P中的资源
  3. _Psyscall:进入系统调用时,go运行时在系统调用前插代码reentersyscall()中设置。
  4. _Pgcstop:当gc停止了整个世界(STW)时, P会变为此状态。
  5. _Pdead:当P的数量在运行时改变(调用procresize(), 且数量减少时多余的P会变为此状态。
1.3 G:实现的核心结构

goroutine,G维护了goroutine需要的栈、程序计数器以及它所在的M等信息。

G一般分配给当前的P,当前P的可执行队列满了以后,G被放入全局队列,同时将本地队列一半移到全局队列。
当P的队列空了的时候,会从全局队列偷取一批(最多32个)到本地队列。

goroutine状态:
  1. _Gidle=0, 刚创建的G,还未初始化。
  2. _Grunnable=1,处于run队列中(本地、全局),还没拥有栈。
  3. _Grunning=2,可以执行代码了,G拥有栈,不在run队列中,并且被分配给了M,P。
  4. _Gsyscall=3,G正在执行一个系统调用,没有执行用户代码,拥有栈,不在run队列,被分配了M。
  5. _Gwaiting=4,G被runtime阻塞,没有执行用户代码,没有在run队列,不拥有栈。
  6. _Gmoribund_unused=5,该状态未使用。
  7. _Gdead=6,G未使用,有三种情况处于该状态:1.刚退出。2.处于free列表。3.刚把初始化。没有执行用户代码。
  8. _Genqueue_unused=7,该状态未使用。
  9. _Gcopystack=8,G的栈正在被移动,没有执行用户代码。
  10. _Gscan=0x1000,
    _Gscanrunnable = _Gscan + _Grunnable // 0x1001
    _Gscanrunning = _Gscan + _Grunning // 0x1002
    _Gscansyscall = _Gscan + _Gsyscall // 0x1003
    _Gscanwaiting = _Gscan + _Gwaiting // 0x1004

2. MPG关系

2.1. G与P

G放在P上才可以被运行。有三种方式获取一个G。

  1. 本地队列P.runq,通过runqget()获取。
  2. 全局队列sched.runq,通过globrunqget()获取。
  3. 从其他P上偷取,通过runqsteal()获取。

当一个G被创建出来,或者变为可执行状态时,就把他放到P的可执行队列中。当一个G执行结束时,P会从队列中把该G取出;
通过P,避免了每次都从全局队列中获取G,避免了全局锁。

2.2. P与M

P要与M关联才可以运行。通过acquirep()将P与M关联。通过releasep()将P与M解绑。
3. 需要绑定在M上才能运行;

2.3 m0,g0

go中有特殊的M和G, 它们是m0和g0。m0负责执行初始化操作和启动第一个G。g0是仅用于负责调度的G, g0不指向任何可执行的函数, 每个m都会有一个自己的g0。

var (
	m0           m
	g0           g
	raceprocctx0 uintptr
)

3. m,p,g结构定义

3.1. m

主要成员如下:

  1. g0 *g,调度和执行系统调用都会切换到g0栈去执行。
  2. curg *g,当前正在执行的goroutine
  3. p puintptr,当前m附着的p,用来执行goroutine。
  4. oldp puintptr,执行系统调用前附着的p。
  5. nextp puintptr: 唤醒M时, M会拥有这个P
  6. park note,线程唤醒用的key。
  7. spinning bool,标识M处于自旋状态
3.2 p

主要成员:

  1. status uint32,p状态
  2. m muintptr,附着在的m
  3. runq [256]guintptr,待执行g对象列表,最多256个。
  4. gFree,可复用g对象列表。
  5. link puintptr, 下一个P.
  6. gcBgMarkWorker,后台GC的worker函数, 如果它存在M会优先执行它
  7. gcw gcWork,
3.3. g

主要成员:

  1. stack,栈空间
  2. sched gobuf,g对象的调度数据,当goroutine被中断时,当前的状态数据保存在这里。
  3. atomicstatus uint32,g的状态
  4. preempt bool,抢占信号

4. 调度器schedule()首次调用

go进程启动后,

  1. 会先进行调度器的初始化schedinit()(见12-go进程启动过程)。
  2. 然后调用runtime·newproc创建一个goroutine,用于执行runtime.main
  3. 然后调用runtime·mstart首次调用schedule(),执行runtime.maingoroutine.
  4. runtime.main中创建一个线程,执行sysmonsysmon会通过netpoll获取网络事件。
  5. runtime.main执行main.main.

5. 调度器调度时机

goroutine的调度通过schedule()进行,触发schedule()调用的时机有:

  1. go程序主动触发
    代码中主动调用runtime.Gosched()

  2. 系统调用
    代码中发起系统调用时,go会在系统调用前后插入代码。
    前插代码:主要设置当前p进入_Psyscall状态,唤醒sysmon,触发gc。将当前p与m解绑。
    后插代码:主要将之前的p与m关联(如果p还没被别的m关联),关联后直接执行。如果之前的P已经被别的m关联,则获取一个空闲的P,获取失败,则调用调度器schedule()

  3. sysmon对p抢占
    sysmon通过retake()来发起对p或者g的抢占,这里只是打上标记,真正的抢占在发生函数调用时,通过newstack()进行:

    1. 对G抢占
      P在同一个G上运行时间超过10MS。就标记G为需要抢占,g.preempt=trueg.stackguard0 = stackPreempt
    2. 对P抢占
      P处于系统调用状态,判断是否需要发起抢占。当超过1个sysmon调度周期(至少20us),当P上有待执行任务,或者发起系统调用时间超过10MS,就会对P发起抢占(handoffp())。handoffp()主要功能就是判断是否需要将P交给另外一个m执行。P上有待执行任务,或者全局队列上有待执行任务,就将P交个另外一个m执行。如果P没有任务可执行,就将P放入空闲列表。
  4. sysmon对异步事件处理
    sysmon会每10MS进行一次netpoll操作,有事件触发时,调用injectglist()将G放入全局待执行列表,如果此时有P处于空闲状态,则针对每个P执行一次startm(nil,false)获取一个空闲的P,如果有空闲的M则唤醒,没有则新建一个M执行P。

  5. select触发gopark()
    当对chan进行select操作时,如果chan上没有数据,则会调用gopark()gopark()中,如果G允许被抢占,则设置抢占标记,并调用park_m()将G与M解除绑定,然后触发调度器调用,schedule()

6. 调度器实现schedule()

  1. 获取当前的G。
  2. 判断是否处于GC中,是则循环等待GC结束。
  3. 调用findRunnableGCWorker(),判断是否可以进行当前p上的gc。
  4. 没有可以进行的gc,则调用globrunqget(),每60次schedule()调用,就优先从全局队列中取可执行的G;防止全局列表中的G得不到执行。
  5. 调用findrunnable(),获取一个可以运行的G。此过程是阻塞的,直到获取到一个可以执行的G。
  6. 调用execute(),执行G。
  7. execute()调用gogo()(src\syscall\asm_linux_amd64.s),gogo()从sched结构恢复G上次被调度器暂停时的寄存器现场(SP、PC等),然后继续执行。
// One round of scheduler: find a runnable goroutine and execute it.
// Never returns.
func schedule() {
    // 1. 获取当前的g。
	_g_ := getg()
top:
    // 2. 判断是否处于gc等待状态。处于等待状态,则通知m线程sleep,并循环判断等待直到gc结束。
	if sched.gcwaiting != 0 {
		gcstopm()
		goto top
	}

	var gp *g
	var inheritTime bool
	
	// 3. 查找traceReader goroutine。找到则设置状态为```_Gwaiting```->```_Grunnable```。
	if trace.enabled || trace.shutdown {
		gp = traceReader()
		if gp != nil {
			casgstatus(gp, _Gwaiting, _Grunnable)
			traceGoUnpark(gp, 0)
		}
	}
	
	// 4.没有traceReader,则查找当前p的后台mark goroutine。
	if gp == nil && gcBlackenEnabled != 0 {
		gp = gcController.findRunnableGCWorker(_g_.m.p.ptr())
	}
	
	// 5. 没有traceReader,也没有gc后台mark goroutine,则从全局待执行队列获取一个g。
	// 优先以60%的概率从全局队列获取。
	if gp == nil {
		// Check the global runnable queue once in a while to ensure fairness.
		// Otherwise two goroutines can completely occupy the local runqueue
		// by constantly respawning each other.
		if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
			lock(&sched.lock)
			gp = globrunqget(_g_.m.p.ptr(), 1)
			unlock(&sched.lock)
		}
	}
	
	// 6. 没有从全局队列获取到g,则从本地队列获取。
	if gp == nil {
		gp, inheritTime = runqget(_g_.m.p.ptr())
        ...
	}
	
	// 7. 调用findrunnable(),获取一个可以运行的G。次过程是阻塞的,直到获取到一个可以执行的G。
	if gp == nil {
		gp, inheritTime = findrunnable() // blocks until work is available
	}

	// This thread is going to run a goroutine and is not spinning anymore,
	// so if it was marked as spinning we need to reset it now and potentially
	// start a new spinning M.
	if _g_.m.spinning {
		resetspinning()
	}

	if sched.disable.user && !schedEnabled(gp) {
		// Scheduling of this goroutine is disabled. Put it on
		// the list of pending runnable goroutines for when we
		// re-enable user scheduling and look again.
		lock(&sched.lock)
		if schedEnabled(gp) {
			// Something re-enabled scheduling while we
			// were acquiring the lock.
			unlock(&sched.lock)
		} else {
			sched.disable.runnable.pushBack(gp)
			sched.disable.n++
			unlock(&sched.lock)
			goto top
		}
	}

	if gp.lockedm != 0 {
		// Hands off own p to the locked m,
		// then blocks waiting for a new p.
		startlockedm(gp)
		goto top
	}
    
    // 8. 执行G。
	execute(gp, inheritTime)
}
6.2. findrunnable()

findrunnable()逻辑比较复杂。主要分为两个阶段top/stop。这里会一直阻塞直到找到可以运行的G。

6.2.1 top阶段
  1. 循环等待gc结束。
  2. 调用runqget(),从当前P的队列中取G,找到就返回。
  3. 调用globrunqget(),从全局队列中取可执行的G,找到就返回。
  4. 调用netpoll(非阻塞调用),判断是否有异步调用结束的G,找到就返回。
    如果发生了异步事件,则调用injectglist(),间接startm()调用,唤醒空闲M,没有空闲的就创建一个新的M线程。
  5. 调用runqsteal(),从其他P的队列中偷取。
6.2.2 stop阶段

如果top阶段没有获取到可以执行的G,则尝试执行GC,如果不需要GC,则尝试从全局队列、异步事件获取G。

  1. 如果处于垃圾回收标记阶段,就进行垃圾回收的标记工作,返回该G。
  2. 调用globrunqget(),从全局队列中取可执行的G,找到就返回。
    此时如果依然没有找到可用的G,则说明当前P处于空闲状态,将其与M解绑,放入空闲列表。
  3. 调用netpoll(阻塞调用),获取异步调用结束的G。
    由第二步知道,此时当前P已经与M解绑,需要重新找到一个空闲的P,找到就返回第一个G,如果没有找到,则通知当前M sleep。并重回top阶段。
    这里如果有异步事件发生,则调用injectglist(),间接startm()调用,获取一个空闲的M唤醒,如果没有空闲的M,则新建一个M线程。

// Finds a runnable goroutine to execute.
// Tries to steal from other P's, get g from global queue, poll network.
func findrunnable() (gp *g, inheritTime bool) {
	_g_ := getg()
top:
	_p_ := _g_.m.p.ptr()
	// 1. 持续等待gc结束。
	if sched.gcwaiting != 0 {
		gcstopm()
		goto top
	}

	// 2. 尝试从本地等待队列获取一个g。找到则返回该g。
	if gp, inheritTime := runqget(_p_); gp != nil {
		return gp, inheritTime
	}

	// 3. 尝试从全局队列获取一个g。全局需要加锁。找到就返回该g。
	if sched.runqsize != 0 {
		lock(&sched.lock)
		gp := globrunqget(_p_, 0)
		unlock(&sched.lock)
		if gp != nil {
			return gp, false
		}
	}

    // 4. 对网络进行poll,判断是否有网络事件发生。如果有则设置g状态```_Gwaiting```->```_Grunnable```。返回第一个g。
	// Poll network.
	// This netpoll is only an optimization before we resort to stealing.
	// We can safely skip it if there are no waiters or a thread is blocked
	// in netpoll already. If there is any kind of logical race with that
	// blocked thread (e.g. it has already returned from netpoll, but does
	// not set lastpoll yet), this thread will do blocking netpoll below
	// anyway.
	if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Load64(&sched.lastpoll) != 0 {
		if list := netpoll(false); !list.empty() { // non-blocking
			gp := list.pop()
			injectglist(&list)
			casgstatus(gp, _Gwaiting, _Grunnable)
			if trace.enabled {
				traceGoUnpark(gp, 0)
			}
			return gp, false
		}
	}

    // 5. 没有网络事件发生,则从其他p偷取一个g。如果此时空闲的p达到```GOMAXPROCS-1```,进入stop阶段。
	// Steal work from other P's.
	procs := uint32(gomaxprocs)
	if atomic.Load(&sched.npidle) == procs-1 {
		// Either GOMAXPROCS=1 or everybody, except for us, is idle already.
		// New work can appear from returning syscall/cgocall, network or timers.
		// Neither of that submits to local run queues, so no point in stealing.
		goto stop
	}
	
	// 6. 如果spinning状态的M数量 >= 繁忙状态P数量,就阻塞,避免CPU过于繁忙。
	// If number of spinning M's >= number of busy P's, block.
	// This is necessary to prevent excessive CPU consumption
	// when GOMAXPROCS>>1 but the program parallelism is low.
	if !_g_.m.spinning && 2*atomic.Load(&sched.nmspinning) >= procs-atomic.Load(&sched.npidle) {
		goto stop
	}
	
	// 7. 如果m不是出于```spinning```状态,则设置为出于```spinning```,并将调度器出于spinning计数器加1。
	if !_g_.m.spinning {
		_g_.m.spinning = true
		atomic.Xadd(&sched.nmspinning, 1)
	}
	
	// 8. 随机从其他p中偷取一个g,会尝试4次。偷到则返回。
	for i := 0; i < 4; i++ {
		for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() {
			if sched.gcwaiting != 0 {
				goto top
			}
			stealRunNextG := i > 2 // first look for ready queues with more than 1 g
			if gp := runqsteal(_p_, allp[enum.position()], stealRunNextG); gp != nil {
				return gp, false
			}
		}
	}

stop:
    // 9. 到目前为止还没找到g,判断是否可以进行gc扫描。
    //  如果p上的gc扫描work可以继续工作,则设置该gc g状态```_Gwaiting```->```_Grunnable```。返回该g。
	// We have nothing to do. If we're in the GC mark phase, can
	// safely scan and blacken objects, and have work to do, run
	// idle-time marking rather than give up the P.
	if gcBlackenEnabled != 0 && _p_.gcBgMarkWorker != 0 && gcMarkWorkAvailable(_p_) {
		_p_.gcMarkWorkerMode = gcMarkWorkerIdleMode
		gp := _p_.gcBgMarkWorker.ptr()
		casgstatus(gp, _Gwaiting, _Grunnable)
		if trace.enabled {
			traceGoUnpark(gp, 0)
		}
		return gp, false
	}

	// wasm only:
	// If a callback returned and no other goroutine is awake,
	// then pause execution until a callback was triggered.
	if beforeIdle() {
		// At least one goroutine got woken.
		goto top
	}

	// Before we drop our P, make a snapshot of the allp slice,
	// which can change underfoot once we no longer block
	// safe-points. We don't need to snapshot the contents because
	// everything up to cap(allp) is immutable.
	allpSnapshot := allp

	// return P and block
	lock(&sched.lock)
	if sched.gcwaiting != 0 || _p_.runSafePointFn != 0 {
		unlock(&sched.lock)
		goto top
	}
	
	// 10. 再次尝试从全局队列获取g,如果全局可运行队列不为空,则从全局队列获取一个g。
	if sched.runqsize != 0 {
		gp := globrunqget(_p_, 0)
		unlock(&sched.lock)
		return gp, false
	}
	
	// 11. 全局队列为空,说明当前p处于空闲状态,则将p与m解绑。
	if releasep() != _p_ {
		throw("findrunnable: wrong p")
	}
	pidleput(_p_)
	unlock(&sched.lock)

	// Delicate dance: thread transitions from spinning to non-spinning state,
	// potentially concurrently with submission of new goroutines. We must
	// drop nmspinning first and then check all per-P queues again (with
	// #StoreLoad memory barrier in between). If we do it the other way around,
	// another thread can submit a goroutine after we've checked all run queues
	// but before we drop nmspinning; as the result nobody will unpark a thread
	// to run the goroutine.
	// If we discover new work below, we need to restore m.spinning as a signal
	// for resetspinning to unpark a new worker thread (because there can be more
	// than one starving goroutine). However, if after discovering new work
	// we also observe no idle Ps, it is OK to just park the current thread:
	// the system is fully loaded so no spinning threads are required.
	// Also see "Worker thread parking/unparking" comment at the top of the file.
	wasSpinning := _g_.m.spinning
	if _g_.m.spinning {
		_g_.m.spinning = false
		if int32(atomic.Xadd(&sched.nmspinning, -1)) < 0 {
			throw("findrunnable: negative nmspinning")
		}
	}

    // 12. 再次检查全局p列表,找到待运行队列不为空的P,同时找到一个空闲的P,将其与当前M关联。回到top阶段尝试让其执行该G。
	// check all runqueues once again
	for _, _p_ := range allpSnapshot {
		if !runqempty(_p_) {
			lock(&sched.lock)
			_p_ = pidleget()
			unlock(&sched.lock)
			if _p_ != nil {
				acquirep(_p_)
				if wasSpinning {
					_g_.m.spinning = true
					atomic.Xadd(&sched.nmspinning, 1)
				}
				goto top
			}
			break
		}
	}

    // 13. 没有找到可以执行的G,则再次尝试进行GC。
	// Check for idle-priority GC work again.
	if gcBlackenEnabled != 0 && gcMarkWorkAvailable(nil) {
		lock(&sched.lock)
		_p_ = pidleget()
		if _p_ != nil && _p_.gcBgMarkWorker == 0 {
			pidleput(_p_)
			_p_ = nil
		}
		unlock(&sched.lock)
		if _p_ != nil {
			acquirep(_p_)
			if wasSpinning {
				_g_.m.spinning = true
				atomic.Xadd(&sched.nmspinning, 1)
			}
			// Go back to idle GC check.
			goto stop
		}
	}


    // 14. 阻塞调用netpoll()。如果有事件发生,并且获取到一个空闲的P,将其与当前M关联,返回第一个G。
	// poll network
	if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Xchg64(&sched.lastpoll, 0) != 0 {
		if _g_.m.p != 0 {
			throw("findrunnable: netpoll with p")
		}
		if _g_.m.spinning {
			throw("findrunnable: netpoll with spinning")
		}
		list := netpoll(true) // block until new work is available
		atomic.Store64(&sched.lastpoll, uint64(nanotime()))
		if !list.empty() {
			lock(&sched.lock)
			_p_ = pidleget()
			unlock(&sched.lock)
			if _p_ != nil {
				acquirep(_p_)
				gp := list.pop()
				// 15. 设置可执行G状态```_Gwaiting```->````_Grunnable```。都放入全局待执行队列。
				//      根据空闲P个数,调用startm()
				injectglist(&list)
				casgstatus(gp, _Gwaiting, _Grunnable)
				if trace.enabled {
					traceGoUnpark(gp, 0)
				}
				return gp, false
			}
			injectglist(&list)
		}
	}
	
	// 16. 将m与p解绑,通知m线程sleep。
	stopm()
	
	// 17. 继续循环,找到可以执行的G。
	goto top
}

7. gcstopm()

  1. 将p与m解绑。
  2. 设置p的状态为_Pgcstop
  3. 通知m线程sleep。

// Stops the current m for stopTheWorld.
// Returns when the world is restarted.
func gcstopm() {
	_g_ := getg()

	if sched.gcwaiting == 0 {
		throw("gcstopm: not waiting for gc")
	}
	if _g_.m.spinning {
		_g_.m.spinning = false
		// OK to just drop nmspinning here,
		// startTheWorld will unpark threads as necessary.
		if int32(atomic.Xadd(&sched.nmspinning, -1)) < 0 {
			throw("gcstopm: negative nmspinning")
		}
	}
	
	// 将p与m解绑
	_p_ := releasep()
	lock(&sched.lock)
	// 设置p的状态为_Pgcstop.
	_p_.status = _Pgcstop
	sched.stopwait--
	if sched.stopwait == 0 {
		notewakeup(&sched.stopnote)
	}
	unlock(&sched.lock)
	// 通知m线程sleep。
	stopm()
}
stopm()
  1. 将调度器当前正在等待工作指针设置为当前m。sched.midle = m。给等待计数器加1. sched.nmidle++.
  2. 通知m线程sleep。
  3. 将m.nextp与m关联。同时置m的nextp为0.

// Stops execution of the current m until new work is available.
// Returns with acquired P.
func stopm() {
	_g_ := getg()

	if _g_.m.locks != 0 {
		throw("stopm holding locks")
	}
	if _g_.m.p != 0 {
		throw("stopm holding p")
	}
	if _g_.m.spinning {
		throw("stopm spinning")
	}

	lock(&sched.lock)
	mput(_g_.m)
	unlock(&sched.lock)
	notesleep(&_g_.m.park)
	noteclear(&_g_.m.park)
	acquirep(_g_.m.nextp.ptr())
	_g_.m.nextp = 0
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值