本文承接上一篇文章【golang内幕之程序启动流程】【https://blog.csdn.net/QQ1130141391/article/details/96197570】

在 【golang内幕之程序启动流程】文中我们提到了在主线程中启动了main Goroutine,并提到了schedule是一轮的协程调度,并且是永不返回的,这正是我们研究的入口点;另外也提到了go func(){}语句创建一个协程,实际上对应的是newproc函数,这是我们研究的另外一个入口点。

现在我们先看Goroutine的状态都有哪些状态,Goroutine状态声明在runtime/runtime2.go文件中:

const (// G status//// Beyond indicating the general state of a G, the G status// acts like a lock on the goroutine's stack (and hence its// ability to execute user code).//// If you add to this list, add to the list// of "okay during garbage collection" status// in mgcmark.go too.// _Gidle means this goroutine was just allocated and has not// yet been initialized._Gidle = iota // 0// _Grunnable means this goroutine is on a run queue. It is// not currently executing user code. The stack is not owned._Grunnable // 1// _Grunning means this goroutine may execute user code. The// stack is owned by this goroutine. It is not on a run queue.// It is assigned an M and a P._Grunning // 2// _Gsyscall means this goroutine is executing a system call.// It is not executing user code. The stack is owned by this// goroutine. It is not on a run queue. It is assigned an M._Gsyscall // 3// _Gwaiting means this goroutine is blocked in the runtime.// It is not executing user code. It is not on a run queue,// but should be recorded somewhere (e.g., a channel wait// queue) so it can be ready()d when necessary. The stack is// not owned *except* that a channel operation may read or// write parts of the stack under the appropriate channel// lock. Otherwise, it is not safe to access the stack after a// goroutine enters _Gwaiting (e.g., it may get moved)._Gwaiting // 4// _Gmoribund_unused is currently unused, but hardcoded in gdb// scripts._Gmoribund_unused // 5// _Gdead means this goroutine is currently unused. It may be// just exited, on a free list, or just being initialized. It// is not executing user code. It may or may not have a stack// allocated. The G and its stack (if any) are owned by the M// that is exiting the G or that obtained the G from the free// list._Gdead // 6// _Genqueue_unused is currently unused._Genqueue_unused // 7// _Gcopystack means this goroutine's stack is being moved. It// is not executing user code and is not on a run queue. The// stack is owned by the goroutine that put it in _Gcopystack._Gcopystack // 8// _Gscan combined with one of the above states other than// _Grunning indicates that GC is scanning the stack. The// goroutine is not executing user code and the stack is owned// by the goroutine that set the _Gscan bit.//// _Gscanrunning is different: it is used to briefly block// state transitions while GC signals the G to scan its own// stack. This is otherwise like _Grunning.//// atomicstatus&~Gscan gives the state the goroutine will// return to when the scan completes._Gscan         = 0x1000_Gscanrunnable = _Gscan + _Grunnable // 0x1001_Gscanrunning  = _Gscan + _Grunning  // 0x1002_Gscansyscall  = _Gscan + _Gsyscall  // 0x1003_Gscanwaiting  = _Gscan + _Gwaiting  // 0x1004
)

从注释中,我们可以看出:

_Gidle:表示刚刚分配了Goroutine内存但没有进行初始化,此时状态为空闲状态。这个我们可以从代码看出:

// Create a new g running fn with narg bytes of arguments starting
// at argp. callerpc is the address of the go statement that created
// this. The new g is put on the queue of g's waiting to run.
func newproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) {_g_ := getg()...//从回收列表中查找goroutine,避免重复创建newg := gfget(_p_)if newg == nil {//回收列表中不存在可用goroutine,则重新分配一个goroutine结构及其栈空间,栈空间使用内核分配newg = malg(_StackMin)//设置状态为死亡状态casgstatus(newg, _Gidle, _Gdead)//加到g列表中allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.}...casgstatus(newg, _Gdead, _Grunnable)...//将go routine放进p任务队列runqput(_p_, newg, true)//由空闲的p,直接唤醒新创建的go routineif atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && mainStarted {wakep()}...
}
func allgadd(gp *g) {if readgstatus(gp) == _Gidle {throw("allgadd: bad status Gidle")}println("allgadd allgs:", allgs, len(allgs), gp)lock(&allglock)allgs = append(allgs, gp)allglen = uintptr(len(allgs))unlock(&allglock)
}

从上面代码可以看出:

刚分配内存的Goroutine状态为空闲状态_Gidle,然后切换状态是死亡状态_Gdead,然后刚创建的Goroutine放入Goroutine列表中,然后切换状态程可运行状态_Grunnable,然后把新创建的Goroutine放入p的本地队列中,等待执行。

总结:新建的Goroutine才会出于空闲状态_Gidle,而且时间特别短。

_Grunnable:可运行状态,在p的本地队列中等待执行。

// Mark gp ready to run.
func ready(gp *g, traceskip int, next bool) {...// status is Gwaiting or Gscanwaiting, make Grunnable and put on runqcasgstatus(gp, _Gwaiting, _Grunnable)runqput(_g_.m.p.ptr(), gp, next)...
}
func findrunnable() (gp *g, inheritTime bool) {...// poll networkif netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Xchg64(&sched.lastpoll, 0) != 0 {...list := netpoll(true) // block until new work is availableatomic.Store64(&sched.lastpoll, uint64(nanotime()))if !list.empty() {...casgstatus(gp, _Gwaiting, _Grunnable)...return gp, false}injectglist(&list)}}...
}
// Injects the list of runnable G's into the scheduler and clears glist.
// Can run concurrently with GC.
func injectglist(glist *gList) {...lock(&sched.lock)var n intfor n = 0; !glist.empty(); n++ {gp := glist.pop()casgstatus(gp, _Gwaiting, _Grunnable)globrunqput(gp)}...
}
func goschedImpl(gp *g) {...casgstatus(gp, _Grunning, _Grunnable)...
}
// exitsyscall slow path on g0.
// Failed to acquire P, enqueue gp as runnable.
//
//go:nowritebarrierrec
func exitsyscall0(gp *g) {...casgstatus(gp, _Gsyscall, _Grunnable)...
}
// Change number of processors. The world is stopped, sched is locked.
// gcworkbufs are not being modified by either the GC or
// the write barrier code.
// Returns list of Ps with local work, they need to be scheduled by the caller.
func procresize(nprocs int32) *p {...for i := nprocs; i < old; i++ {...if gp := p.gcBgMarkWorker.ptr(); gp != nil {casgstatus(gp, _Gwaiting, _Grunnable)...globrunqput(gp)...}...}...
}
// findRunnableGCWorker returns the background mark worker for _p_ if it
// should be run. This must only be called when gcBlackenEnabled != 0.
func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g {...casgstatus(gp, _Gwaiting, _Grunnable)...
}
func newproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) {...casgstatus(newg, _Gdead, _Grunnable)...
}

涉及到切换到_Grunnable的代码点如上所示,可以看出有4中切换:

1、_Gdead -> _Grunnable

2、_Gsyscall -> _Grunnable

3、_Gwaiting -> _Grunnable

第一种,上文已经提及到,新创建的Goroutine会从_Gdead切换到_Grunnable

接下来,我们分析什么时机会触发_Gwaiting -> _Grunnable 、_Gsyscall -> _Grunnable和_Grunning -> _Grunnable  的状态切换。

第二种,_Gsyscall -> _Grunnable

跟踪 exitsyscall0函数:

exitsyscall -> exitsyscall0
//lock_sema.go
func notetsleepg(n *note, ns int64) bool {...entersyscallblock()ok := notetsleep_internal(n, ns, nil, 0)exitsyscall()return ok
}
//lock_futex.go
func notetsleepg(n *note, ns int64) bool {...entersyscallblock()ok := notetsleep_internal(n, ns)exitsyscall()return ok
}

总结:调用了阻塞的系统调用后会进入_Gsyscall,从阻塞系统调用唤醒后,会从_Gsyscall切换到_Grunnable。

func park_m(gp *g) {...casgstatus(gp, _Grunning, _Gwaiting)dropg()if _g_.m.waitunlockf != nil {...if !ok {if trace.enabled {traceGoUnpark(gp, 2)}casgstatus(gp, _Gwaiting, _Grunnable)execute(gp, true) // Schedule it back, never returns.}}schedule()
}

gopark -> park_m

触发gopark的点比较多,如下:

//*gopark
//chansend(chan.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//chanrecv(chan.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//gcBgMarkWorker(mgc.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//notetsleepg(lock_js.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//init(lock_js.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//handleEvent(lock_js.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//netpollblock(netpoll.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//main(proc.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//goparkunlock(proc.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//block(select.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//selectgo(select.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//notetsleepg(lock_sema.go -> (entersyscallblock - notetsleep_internal - exitsyscall)
//notetsleepg(lock_futext.go -> (entersyscallblock - notetsleep_internal - exitsyscall)

可以看出,基本是调用了阻塞接口后,会触发gopark进行状态切换,从这里也可以看的出,有哪些是阻塞行操作。

func park_m(gp *g) {_g_ := getg()if trace.enabled {traceGoPark(_g_.m.waittraceev, _g_.m.waittraceskip)}casgstatus(gp, _Grunning, _Gwaiting)dropg()if _g_.m.waitunlockf != nil {fn := *(*func(*g, unsafe.Pointer) bool)(unsafe.Pointer(&_g_.m.waitunlockf))ok := fn(gp, _g_.m.waitlock)_g_.m.waitunlockf = nil_g_.m.waitlock = nilif !ok {if trace.enabled {traceGoUnpark(gp, 2)}casgstatus(gp, _Gwaiting, _Grunnable)execute(gp, true) // Schedule it back, never returns.}}schedule()
}

总结:先从_Grunning切换_Gwaiting,再执行阻塞性操作,阻塞行操作唤醒后,切换成_Grunnable。

_Grunning:代表正在执行程序指令,即Goroutine正在运行,此时Goroutine不在p运行队列中,并取得m、p运行资源,即与m和p完成绑定关系,Goroutine完全使用所在m(线程)的栈空间。

func newstack() {...if preempt {...// Synchronize with scang.casgstatus(gp, _Grunning, _Gwaiting)if gp.preemptscan {...casfrom_Gscanstatus(gp, _Gscanwaiting, _Gwaiting)// This clears gcscanvalid.casgstatus(gp, _Gwaiting, _Grunning)gp.stackguard0 = gp.stack.lo + _StackGuardgogo(&gp.sched) // never return}// Act like goroutine called runtime.Gosched.casgstatus(gp, _Gwaiting, _Grunning)gopreempt_m(gp) // never return}...casgstatus(gp, _Grunning, _Gcopystack)...copystack(gp, newsize, true)...casgstatus(gp, _Gcopystack, _Grunning)gogo(&gp.sched)
}
runtime·morestack_noctxt -> runtime·morestack -> newstack

 runtime·morestack -> newstack    至于何时调用morestack,这是由golang编译器和链接器完成的,我们可以查看stack.go的注释说明:

//stack.go
/*
Stack layout parameters.
Included both by runtime (compiled via 6c) and linkers (compiled via gcc).The per-goroutine g->stackguard is set to point StackGuard bytes
above the bottom of the stack.  Each function compares its stack
pointer against g->stackguard to check for overflow.  To cut one
instruction from the check sequence for functions with tiny frames,
the stack is allowed to protrude StackSmall bytes below the stack
guard.  Functions with large frames don't bother with the check and
always call morestack.  The sequences are (for amd64, others are
similar):
*/

从注释可以看出,每个函数都会检查栈是否溢出。

我们在细看一下newstack,看看满足什么条件下会触发状态切换:

func newstack() {thisg := getg()// TODO: double check all gp. shouldn't be getg().if thisg.m.morebuf.g.ptr().stackguard0 == stackFork {throw("stack growth after fork")}if thisg.m.morebuf.g.ptr() != thisg.m.curg {print("runtime: newstack called from g=", hex(thisg.m.morebuf.g), "\n"+"\tm=", thisg.m, " m->curg=", thisg.m.curg, " m->g0=", thisg.m.g0, " m->gsignal=", thisg.m.gsignal, "\n")morebuf := thisg.m.morebuftraceback(morebuf.pc, morebuf.sp, morebuf.lr, morebuf.g.ptr())throw("runtime: wrong goroutine in newstack")}gp := thisg.m.curgif thisg.m.curg.throwsplit {// Update syscallsp, syscallpc in case traceback uses them.morebuf := thisg.m.morebufgp.syscallsp = morebuf.spgp.syscallpc = morebuf.pcpcname, pcoff := "(unknown)", uintptr(0)f := findfunc(gp.sched.pc)if f.valid() {pcname = funcname(f)pcoff = gp.sched.pc - f.entry}print("runtime: newstack at ", pcname, "+", hex(pcoff)," sp=", hex(gp.sched.sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n","\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n","\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n")thisg.m.traceback = 2 // Include runtime framestraceback(morebuf.pc, morebuf.sp, morebuf.lr, gp)throw("runtime: stack split at bad time")}morebuf := thisg.m.morebufthisg.m.morebuf.pc = 0thisg.m.morebuf.lr = 0thisg.m.morebuf.sp = 0thisg.m.morebuf.g = 0// NOTE: stackguard0 may change underfoot, if another thread// is about to try to preempt gp. Read it just once and use that same// value now and below.preempt := atomic.Loaduintptr(&gp.stackguard0) == stackPreempt// Be conservative about where we preempt.// We are interested in preempting user Go code, not runtime code.// If we're holding locks, mallocing, or preemption is disabled, don't// preempt.// This check is very early in newstack so that even the status change// from Grunning to Gwaiting and back doesn't happen in this case.// That status change by itself can be viewed as a small preemption,// because the GC might change Gwaiting to Gscanwaiting, and then// this goroutine has to wait for the GC to finish before continuing.// If the GC is in some way dependent on this goroutine (for example,// it needs a lock held by the goroutine), that small preemption turns// into a real deadlock.if preempt {if thisg.m.locks != 0 || thisg.m.mallocing != 0 || thisg.m.preemptoff != "" || thisg.m.p.ptr().status != _Prunning {// Let the goroutine keep running for now.// gp->preempt is set, so it will be preempted next time.gp.stackguard0 = gp.stack.lo + _StackGuardgogo(&gp.sched) // never return}}if gp.stack.lo == 0 {throw("missing stack in newstack")}sp := gp.sched.spif sys.ArchFamily == sys.AMD64 || sys.ArchFamily == sys.I386 || sys.ArchFamily == sys.WASM {// The call to morestack cost a word.sp -= sys.PtrSize}if stackDebug >= 1 || sp < gp.stack.lo {print("runtime: newstack sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n","\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n","\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n")}if sp < gp.stack.lo {print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->status=", hex(readgstatus(gp)), "\n ")print("runtime: split stack overflow: ", hex(sp), " < ", hex(gp.stack.lo), "\n")throw("runtime: split stack overflow")}if preempt {if gp == thisg.m.g0 {throw("runtime: preempt g0")}if thisg.m.p == 0 && thisg.m.locks == 0 {throw("runtime: g is running but p is not")}// Synchronize with scang.casgstatus(gp, _Grunning, _Gwaiting)if gp.preemptscan {for !castogscanstatus(gp, _Gwaiting, _Gscanwaiting) {// Likely to be racing with the GC as// it sees a _Gwaiting and does the// stack scan. If so, gcworkdone will// be set and gcphasework will simply// return.}if !gp.gcscandone {// gcw is safe because we're on the// system stack.gcw := &gp.m.p.ptr().gcwscanstack(gp, gcw)gp.gcscandone = true}gp.preemptscan = falsegp.preempt = falsecasfrom_Gscanstatus(gp, _Gscanwaiting, _Gwaiting)// This clears gcscanvalid.casgstatus(gp, _Gwaiting, _Grunning)gp.stackguard0 = gp.stack.lo + _StackGuardgogo(&gp.sched) // never return}// Act like goroutine called runtime.Gosched.casgstatus(gp, _Gwaiting, _Grunning)gopreempt_m(gp) // never return}// Allocate a bigger segment and move the stack.oldsize := gp.stack.hi - gp.stack.lonewsize := oldsize * 2if newsize > maxstacksize {print("runtime: goroutine stack exceeds ", maxstacksize, "-byte limit\n")throw("stack overflow")}// The goroutine must be executing in order to call newstack,// so it must be Grunning (or Gscanrunning).casgstatus(gp, _Grunning, _Gcopystack)// The concurrent GC will not scan the stack while we are doing the copy since// the gp is in a Gcopystack status.copystack(gp, newsize, true)if stackDebug >= 1 {print("stack grow done\n")}casgstatus(gp, _Gcopystack, _Grunning)gogo(&gp.sched)
}

总结:golang每个函数都会进行栈是否溢出检查,栈空间需要扩容时会newstack,此时会检查弱抢占式gc,会出发Goroutine状态切换_Grunning -> _Gwaiting -> _Grunning。

func writeheapdump_m(fd uintptr) {_g_ := getg()casgstatus(_g_.m.curg, _Grunning, _Gwaiting)...updatememstats()...mdump()casgstatus(_g_.m.curg, _Gwaiting, _Grunning)
}
runtime_debug_WriteHeapDump -> writeheapdump_m

当将堆信息、gc roots、线程、finalizers等信息dump到文件时会出发状态切换。

//gc相关操作
func markroot(gcw *gcWork, i uint32) {...switch {...default:...systemstack(func() {...selfScan := gp == userG && readgstatus(userG) == _Grunningif selfScan {casgstatus(userG, _Grunning, _Gwaiting)userG.waitreason = waitReasonGarbageCollectionScan}...scang(gp, gcw)if selfScan {casgstatus(userG, _Gwaiting, _Grunning)}})}
}
//gc相关操作
func gcAssistAlloc1(gp *g, scanWork int64) {...casgstatus(gp, _Grunning, _Gwaiting)gp.waitreason = waitReasonGCAssistMarkinggcw := &getg().m.p.ptr().gcwworkDone := gcDrainN(gcw, scanWork)casgstatus(gp, _Gwaiting, _Grunning)...
}
//gc相关操作
func gcMarkDone() {...gcMarkDoneFlushed = 0systemstack(func() {...casgstatus(gp, _Grunning, _Gwaiting)forEachP(func(_p_ *p) {...})casgstatus(gp, _Gwaiting, _Grunning)})...
}
//gc相关操作
func gcMarkTermination(nextTriggerRatio float64) {...casgstatus(gp, _Grunning, _Gwaiting)gp.waitreason = waitReasonGarbageCollection...systemstack(func() {gcMark(startTime)// Must return immediately.// The outer function's stack may have moved// during gcMark (it shrinks stacks, including the// outer function's stack), so we must not refer// to any of its variables. Return back to the// non-system stack to pick up the new addresses// before continuing.})systemstack(func() {...setGCPhase(_GCoff)gcSweep(work.mode)})_g_.m.traceback = 0casgstatus(gp, _Gwaiting, _Grunning)...
}
//gc相关操作
func gcBgMarkWorker(_p_ *p) {...for {...systemstack(func() {casgstatus(gp, _Grunning, _Gwaiting)...casgstatus(gp, _Gwaiting, _Grunning)})...}
}
总结:以上均是gc操作,进行相关gc操作时,都会将Goroutine先切换成_Gwaiting,gc操作完成后再切换会_Grunning。

由此可见,gc操作对应用性能的影响是很大的,毕竟gc需要停止所有正在运行的gc,进行完gc操作再切换回去,虽然golang gc部分已经做了很大的优化,gc耗时也已经很短,但编写golang程序时,仍需注意避免过于动态分配内存,导致过多的gc。针对这个问题,有很多解决方案,比较通用的是使用bufferpool,一次性申请大内存,每次需要动态内存时,从bufferpool取,由于bufferpool一直有引用,因此不会触发gc回收。此做法虽然极大降低了gc带来了性能问题,但也需要额外锁操作的消耗。

func execute(gp *g, inheritTime bool) {...casgstatus(gp, _Grunnable, _Grunning)...gogo(&gp.sched)
}

总结:Goroutine得到调度机会后会完成从_Grunnable到_Grunning的切换。

_Gsyscall:也说明Goroutine,但不是执行用户层代码,也是正在执行系统调用,此时Goroutine不在p运行队列中,完全拥有m的栈空间。

func reentersyscall(pc, sp uintptr) {_g_ := getg()println("reentersyscall: ", _g_.goid)...casgstatus(_g_, _Grunning, _Gsyscall)...
}
entersyscall -> reentersyscall
func entersyscallblock() {...casgstatus(_g_, _Grunning, _Gsyscall)...
}

_Gwaiting:此时Goroutine没有执行用户代码,即不在p运行队列,只是某个时刻打个标。

上面提到的gc操作和dumpheap操作就涉及_Grunning到_Gwaiting和_Gwaiting到_Grunning的切换。

涉及切换状态的代码如下:

_Grunning -> _Gwaiting
newstatck
writeheapdump_m
gcMarkTermination
gcBgMarkWorker
park_m
markroot
gcAssistAlloc1_Gwaiting -> _Grunning
newstack
writeheapdump_m
gcMarkDone
gcMarkTermination
gcBgMarkWorker
markroot
gcAssistAlloc1
-----------------------_Grunning -> _Gsyscall
reentersyscall
entersyscallblock_Gsyscall -> _Grunning
exitsyscall
-----------------------_Gsyscall -> _Grunnable
exitsyscall0
-----------------------_Grunning -> _Gcopystack
newstatck_Gcopystack -> _Grunning
newstatck
-----------------------_Gwaiting -> _Grunnable
ready
findrunnable
injectglist
schedule
park_m
procresize
checkdead
-----------------------_Grunnable -> _Grunning
execute_Grunning -> _Grunnable
lock -> gosched_m -> goschedImpl
Gosched -> gosched_m -> goschedImpl
goschedguarded -> goschedguarded_m -> goschedImpl
gopreempt_m -> goschedImpl-----------------------
_Grunning -> _Gdead
goexit -> goexit1 -> goexit0
Goexit -> goexit1 -> goexit0-----------------------_Gdead -> _Gsyscall
needm_Gsyscall -> _Gdead
dropm
-----------------------_Gdead -> _Grunnable
newproc1-----------------------_Gidle -> _Gdead
oneNewExtraM
newproc1
-----------------------

汇总成一张状态切换图:

总结:

1、golang中的go语句实际上对应的代码是newproc,通过newproc会新创建一个Goroutine,分配内存空间,此时状态为_GIdle,然后切换为_Gdead,接着切换状态为_Grunnable并将新创建的Goroutine放入p的运行队列中,等待调度机会;

2、在适合的机会下,即存在空闲的p和空闲的m,会切换为_Grunning(execute),即运行Goroutine;

3、Goroutine的入口函数执行完毕,即从入口函数返回了,会调用goexit来执行真正的退出(释放锁、内存、绑定的m/p资源等);

4、正在运行的Goroutine调用了阻塞的系统调用,在调用系统调用之前,会从_Grunning切换到_Gsyscall(entersyscall),等从系统调用唤醒(notetsleep_internal)后。如果现在不存在可用的p,则会从_Gsyscall切换会_Grunning(exitsyscall),否则会切换为_Grunnable(exitsyscall);

5、needm/dropm是在使用cgo的情况才会涉及,这里不展开讨论;

6、在gc(垃圾回收器)进行gc操作时,需正对正在运行的Goroutine先从_Grunning切换到_Gwating,gc操作完毕后,再切换回_Grunning;

7、在进行dumpheap操作时,需正对正在运行的Goroutine先从_Grunning切换到_Gwating,dumpheap操作完毕后,再切换回_Grunning;

8、morestack/newstack,golang编译器/链接器会在每个函数加入这段代码,如果有必要扩容栈空间,则会执行栈空间扩容操作,扩容前,会先从_Grunning切换到_Gwating,扩容完成后,再切换回_Grunning;因为每个函数都会调用,所以gc在newstack加上了抢占式操作,也会先从_Grunning切换到_Gwating,操作完成后再切换回_Grunning(gopreempt_m)。这个机制在一定程度上避免某个Goroutine真的会出现饿死的情况;但是如果代码执行了for {//没有任务函数调用},这种害死整个m的情况,谁也没更好办法;

9、正在运行的Goroutine调用了阻塞操作,会导致gopark,gopark会释放当前绑定的m,即放弃运行继续,从_Grunning切换程_Gwaiting,调用gopark的情况如下:

//chansend(chan.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//chanrecv(chan.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//gcBgMarkWorker(mgc.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//notetsleepg(lock_js.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//init(lock_js.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//handleEvent(lock_js.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//netpollblock(netpoll.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//main(proc.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//goparkunlock(proc.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//block(select.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go
//selectgo(select.go -> gopark(proc.go -> park_m(proc.go -> schedule(proc.go

10、调用goready情况如下:

//goready
//goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//send(chan.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//closechan(chan.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//recv(chan.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//poll_runtime_pollSetDeadline(netpoll.go -> netpollgoready(netpoll.go -> goready(proc.go  -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//poll_runtime_pollUnblock(netpoll.go -> netpollgoready(netpoll.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//netpolldeadlineimpl(netpoll.go -> netpollgoready(netpoll.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//notewakeup(lock_js.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//checkTimeouts(lock_js.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//beforeIdle(lock_js.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//goroutineReady(time.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//addtimerLocked(time.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go
//readyWithTime(sema.go -> goready(proc.go -> ready(proc.go -> runqput(proc.go -> wakeup(proc.go

11、schedule保证了不会退出;

12、deadlock会进行死锁检测;

本文就是就Goroutine状态切换进行说明,提到的p和m并没有细说,接下来需要研究p和m。

golang内幕之协程状态切换相关推荐

  1. 网络编程基础--协程--greenlet切换---gevent自动识别 IO ---

    协程: 1 单线程来实现并发---协程: 协程:是单线程下的并发,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程, 即协程是由用户程序自己控制调度的 只 ...

  2. golang实现多协程下载文件(支持断点续传)

    golang实现多协程下载文件(支持断点续传) 引言 写这篇文章主要是周末休息太无聊,看了看别人代码,发现基本上要么是多协程下载文件要么就只有单协程的断点续传,所以就试了试有进度条的多协程下载文件(支 ...

  3. 【Kotlin 协程】协程取消 ② ( CPU 密集型协程任务取消 | 使用 isActive 判定协程状态 | 使用 ensureActive 函数取消协程 | 使用 yield 函数取消协程 )

    文章目录 一.CPU 密集型协程任务取消 二.使用 isActive 判定当前 CPU 密集型协程任务是否取消 三.使用 ensureActive 自动处理协程退出 四.使用 yield 函数检查协程 ...

  4. Kotlin 协程调度切换线程是时候解开真相了

    前言 协程系列文章: 一个小故事讲明白进程.线程.Kotlin 协程到底啥关系? 少年,你可知 Kotlin 协程最初的样子? 讲真,Kotlin 协程的挂起/恢复没那么神秘(故事篇) 讲真,Kotl ...

  5. Golang —— goroutine(协程)和channel(管道)

    协程(goroutine) 协程(goroutine)是Go中应用程序并发处理的部分,它可以进行高效的并发运算. 协程是轻量的,比线程更廉价.使用4K的栈内存就可以在内存中创建. 能够对栈进行分割,动 ...

  6. 动手实现Kotlin协程同步切换线程,以及Kotlin协程是如何实现线程切换的

    前言 突发奇想想搞一个同步切换线程的Kotlin协程,而不用各种withContext(){},可以减少嵌套且逻辑更清晰,想实现的结果如下图: 分析 实现我们想要的结果,首先需要知道协程为什么可以控制 ...

  7. golang中通知协程退出的方式

    1 需求分析 go语言中通知子 goroutine 退出的三种方式 方式1 通过全局变量:如果全局变量为真就退出 方式2 通过通道:协程在通道里面取到true就退出 方式3 通过context:通过调 ...

  8. golang内幕之for-go-statement

    func ForGoStatement_1() {go func() {fmt.Println("go-func-1")}() }func main() {ForGoStateme ...

  9. golang 协程同步 简介

    目录 协程概念简要理解 为什么要做同步 协程的几种同步方法 Mutex channel WaitGroup 协程概念简要理解 协程类似线程,是一种更为轻量级的调度单位,但协程还是不同于线程的,线程是系 ...

最新文章

  1. UVA140 Bandwidth带宽
  2. C# ASP.NET MVC 微信和支付宝H5支付开发及Demo
  3. c++常用知识点,易错点,面试常问点
  4. 第 7 节:前端面试指南 — 微信小程序篇(附面试题答案)
  5. MySQL SQL 优化命令行问题 SQL 抓取方式
  6. python数组内运算_有效的数学运算在Python中用cython进行小数组运算
  7. Java计算两个经纬度间的距离
  8. erlang四大behaviour之四-supervisor(转载)
  9. 碧桂园博智林机器人总部大楼_博智林机器人谷总部大楼完工
  10. 部编版是什么版本_部编版教材和人教版教材有什么区别
  11. 量子化信息素蚁群优化特征选择算法
  12. 语音模块SYN6288
  13. Postgresql使用技巧
  14. 无聊日常——对QQ邮箱盗号邮件的垃圾账号填充
  15. cad2006计算机丢失,win10系统无法打开CAD2006提示“计算机中丢失ac1st16.dll”的解决方法...
  16. Hibernate 的 could not initialize proxy - the owning Session was closed问题
  17. 为什么没人再提勤劳致富了?
  18. Web安全学习Week12
  19. 通过Mycelipse,用Hibernate反向生成映射文件、Javabean等
  20. Python123.io---斐波纳契数列 I

热门文章

  1. buildroot制作树莓派CM3的系统
  2. java 叠加层_java简单设置图层实现图片叠加
  3. 11月29日至12月12日总结
  4. win10截图相关教程
  5. 抗渗等级p6是什么意思_混凝土防水等级S6,P6分别是什么意思
  6. 【Python爬虫实战】使用Selenium爬取QQ音乐歌曲及评论信息
  7. 在为订单 7000009确定实际成本中出错
  8. axure sketch 对比_Sketch to Axure RP插件下载
  9. 奋斗者——一个高级咨询师是怎样炼成的
  10. EasyAR笔记01 检测云识别是否存在相似图片