G - goroutine.M - worker thread, or machine.P - processor, a resource that is required to execute Go code. M must have an associated P to execute Go code, however it can be blocked or in a syscall w/o an associated P.
// defined constantsconst (// 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.//// TODO(austin): The _Gscan bit could be much lighter-weight.// For example, we could choose not to run _Gscanrunnable// goroutines found in the run queue, rather than CAS-looping// until they become _Grunnable. And transitions like// _Gscanwaiting -> _Gscanrunnable are actually okay because// they don't affect stack ownership.// _Gidle means this goroutine was just allocated and has not// yet been initialized.// 表示G刚刚新建, 仍未初始化 _Gidle =iota// 0// _Grunnable means this goroutine is on a run queue. It is// not currently executing user code. The stack is not owned.// 表示G在运行队列中, 等待M取出并运行 _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.// 表示M正在运行这个G, 这时候M会拥有一个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.// 表示M正在运行这个G发起的系统调用, 这时候M并不拥有P _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).// 表示G在等待某些条件完成, 这时候G不在运行也不在运行队列中(可能在channel的等待队列中) _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.// 表示G未被使用, 可能已执行完毕(并在freelist中等待下次复用) _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.// 表示G正在获取一个新的栈空间并把原来的内容复制过去(用于防止GC扫描) _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)
接下来是P的状态。P的状态相比G来说,简单许多。
const (// P status// 当M发现无待运行的G时会进入休眠, 这时M拥有的P会变为空闲并加到空闲P链表中 _Pidle =iota// 当M拥有了一个P后, 这个P的状态就会变为运行中, M运行G会使用这个P中的资源 _Prunning // Only this P is allowed to change from _Prunning.// 当go调用原生代码, 原生代码又反过来调用go代码时, 使用的P会变为此状态 _Psyscall// 当gc停止了整个世界(STW)时, P会变为此状态 _Pgcstop// 当P的数量在运行时改变, 且数量减少时多余的P会变为此状态 _Pdead)
go 的运行时包含了一些汇编的内容,在源码中有汇编代码,我们可以自编译。也可以使用GDB调试来实验go的运行时,这里我们暂时不深入。所以,如果要看goroutine创建的话,一定要结合GDB调试。补充一点:go在build的时候,是把自己的运行时也一起build到bin中了,所以go的可执行文件一般比较大。
首先是main goroutine
// The main goroutine.funcmain() {// 获取一个G,一般就是main g := getg()// Racectx of m0->g0 is used only as the parent of the main goroutine.// It must not be used for anything else. g.m.g0.racectx =0//// Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.// Using decimal instead of binary GB and MB because// they look nicer in the stack overflow failure message.if sys.PtrSize ==8 { maxstacksize =1000000000 } else { maxstacksize =250000000 }// Allow newproc to start new Ms. mainStarted =trueif GOARCH !="wasm" { // no threads on wasm yet, so no sysmon systemstack(func() { newm(sysmon, nil) }) }// Lock the main goroutine onto this, the main OS thread,// during initialization. Most programs won't care, but a few// do require certain calls to be made by the main thread.// Those can arrange for main.main to run in the main thread// by calling runtime.LockOSThread during initialization// to preserve the lock. lockOSThread()if g.m !=&m0 { throw("runtime.main not on m0") } doInit(&runtime_inittask) // must be before deferif nanotime() ==0 { throw("nanotime returning zero") }// Defer unlock so that runtime.Goexit during init does the unlock too. needUnlock :=truedeferfunc() {if needUnlock { unlockOSThread() } }()// Record when the world started. runtimeInitTime = nanotime()// 开启GC gcenable() main_init_done =make(chanbool)// 进行一些检查if iscgo {if _cgo_thread_start ==nil { throw("_cgo_thread_start missing") }if GOOS !="windows" {if _cgo_setenv ==nil { throw("_cgo_setenv missing") }if _cgo_unsetenv ==nil { throw("_cgo_unsetenv missing") } }if _cgo_notify_runtime_init_done ==nil { throw("_cgo_notify_runtime_init_done missing") }// Start the template thread in case we enter Go from// a C-created thread and need to create a new thread. startTemplateThread() cgocall(_cgo_notify_runtime_init_done, nil) } doInit(&main_inittask)close(main_init_done) needUnlock =false unlockOSThread()if isarchive || islibrary {// A program compiled with -buildmode=c-archive or c-shared// has a main, but it is not executed.return } fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
fn()// 调试的if raceenabled { racefini() }// Make racy client program work: if panicking on// another goroutine at the same time as main returns,// let the other goroutine finish printing the panic trace.// Once it does, it will exit. See issues 3934 and 20018.if atomic.Load(&runningPanicDefers) !=0 {// Running deferred functions should not take long.for c :=0; c <1000; c++ {if atomic.Load(&runningPanicDefers) ==0 {break } Gosched() } }if atomic.Load(&panicking) !=0 { gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1) } exit(0)for {var x *int32*x =0 }}
如何创建一个goroutine呢?
// Create a new g running fn with siz bytes of arguments.// Put it on the queue of g's waiting to run.// The compiler turns a go statement into a call to this.// Cannot split the stack because it assumes that the arguments// are available sequentially after &fn; they would not be// copied if a stack split occurred.//go:nosplitfuncnewproc(siz int32, fn *funcval) { argp := add(unsafe.Pointer(&fn), sys.PtrSize) gp := getg() pc := getcallerpc() systemstack(func() { newproc1(fn, (*uint8)(argp), siz, gp, pc) })}// 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.funcnewproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) { _g_ := getg()if fn ==nil { _g_.m.throwing =-1// do not dump full stacks throw("go of nil func value") } acquirem() // disable preemption because it can be holding p in a local var siz := narg siz = (siz +7) &^7// We could allocate a larger initial stack if necessary.// Not worth it: this is almost always an error.// 4*sizeof(uintreg): extra space added below// sizeof(uintreg): caller's LR (arm) or return address (x86, in gostartcall).if siz >= _StackMin-4*sys.RegSize-sys.RegSize { throw("newproc: function arguments too large for new goroutine") } _p_ := _g_.m.p.ptr() newg := gfget(_p_)if newg ==nil { newg = malg(_StackMin) casgstatus(newg, _Gidle, _Gdead) allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack. }if newg.stack.hi ==0 { throw("newproc1: newg missing stack") }if readgstatus(newg) != _Gdead { throw("newproc1: new g is not Gdead") } totalSize :=4*sys.RegSize +uintptr(siz) + sys.MinFrameSize // extra space in case of reads slightly beyond frame totalSize +=-totalSize & (sys.SpAlign -1) // align to spAlign sp := newg.stack.hi - totalSize spArg := spif usesLR {// caller's LR*(*uintptr)(unsafe.Pointer(sp)) =0 prepGoExitFrame(sp) spArg += sys.MinFrameSize }if narg >0 { memmove(unsafe.Pointer(spArg), unsafe.Pointer(argp), uintptr(narg))// This is a stack-to-stack copy. If write barriers// are enabled and the source stack is grey (the// destination is always black), then perform a// barrier copy. We do this *after* the memmove// because the destination stack may have garbage on// it.if writeBarrier.needed &&!_g_.m.curg.gcscandone { f := findfunc(fn.fn) stkmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps))if stkmap.nbit >0 {// We're in the prologue, so it's always stack map index 0. bv := stackmapdata(stkmap, 0) bulkBarrierBitmap(spArg, spArg, uintptr(bv.n)*sys.PtrSize, 0, bv.bytedata) } } } memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched)) newg.sched.sp = sp newg.stktopsp = sp newg.sched.pc = funcPC(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function newg.sched.g = guintptr(unsafe.Pointer(newg)) gostartcallfn(&newg.sched, fn) newg.gopc = callerpc newg.ancestors = saveAncestors(callergp) newg.startpc = fn.fnif _g_.m.curg !=nil { newg.labels = _g_.m.curg.labels }if isSystemGoroutine(newg, false) { atomic.Xadd(&sched.ngsys, +1) } newg.gcscanvalid =false casgstatus(newg, _Gdead, _Grunnable)if _p_.goidcache == _p_.goidcacheend {// Sched.goidgen is the last allocated id,// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].// At startup sched.goidgen=0, so main goroutine receives goid=1. _p_.goidcache = atomic.Xadd64(&sched.goidgen, _GoidCacheBatch) _p_.goidcache -= _GoidCacheBatch -1 _p_.goidcacheend = _p_.goidcache + _GoidCacheBatch } newg.goid =int64(_p_.goidcache) _p_.goidcache++if raceenabled { newg.racectx = racegostart(callerpc) }if trace.enabled { traceGoCreate(newg, newg.startpc) } runqput(_p_, newg, true)if atomic.Load(&sched.npidle) !=0&& atomic.Load(&sched.nmspinning) ==0&& mainStarted { wakep() } releasem(_g_.m)}