G是Goroutine的缩写,相当于操作系统的进程控制块(process control block)。它包含:函数执行的指令和参数,任务对象,线程上下文切换,字段保护,和字段的寄存器。
下面代码来自runtime/runtime2.go,可以看到,每个Goroutine都有一个不导出的goid。
type g struct { m *m // current m; offset known to arm liblink sched gobuf ... param unsafe.Pointer // passed parameter on wakeup goid int64 ... vdsoSP uintptr // SP for traceback while in VDSO call (0 if not in call) vdsoPC uintptr // PC for traceback while in VDSO call}复制代码
不同版本的Go语言,Goroutine的栈空间的默认值不一样。下面代码来自runtime/proc.go。
const ( _StackMin = 2048)// Create a new g in state _Grunnable, starting at fn, with narg bytes// of arguments starting at argp. callerpc is the address of the go// statement that created this. The caller is responsible for adding// the new g to the scheduler.//// This must run on the system stack because it's the continuation of// newproc, which cannot split the stack.////go:systemstackfunc newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) *g { _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") } ...}复制代码M
M是一个线程,每个M都有一个线程的栈。如果没有给线程的栈分配内存,操作系统会给线程的栈分配默认的内存。当线程的栈制定,M.stack->G.stack, M的PC寄存器会执行G提供的函数。
type m struct { /* g0的线程栈与M相关 */ g0 *g Curg *g //M 现在绑定的G // SP, PC registers for on-site protection and on-site recovery vdsoSP uintptr vdsoPC uintptr ...}复制代码P
P决定同时执行的任务的数量,GOMAXPROCS限制系统线程执行用户层面的任务的数量。
// GOMAXPROCS sets the maximum number of CPUs that can be executing// simultaneously and returns the previous setting. If n < 1, it does not// change the current setting.// The number of logical CPUs on the local machine can be queried with NumCPU.// This call will go away when the scheduler improves.func GOMAXPROCS(n int) int { if GOARCH == "wasm" && n > 1 { n = 1 // WebAssembly has no threads yet, so only one CPU is possible. } lock(&sched.lock) ret := int(gomaxprocs) unlock(&sched.lock) if n <= 0 || n == ret { return ret } stopTheWorldGC("GOMAXPROCS") // newprocs will be processed by startTheWorld newprocs = int32(n) startTheWorldGC() return ret}复制代码Go调度器的调度过程
sysmon会进入一个无限循环,第一轮休眠20us,然后休眠时间倍乘,最后每次休眠时间达到10ms。sysmon有netpoll, retake(抢占),forcegc, scavenge heap等其他处理。
// Always runs without a P, so write barriers are not allowed.////go:nowritebarrierrecfunc sysmon() { lock(&sched.lock) sched.nmsys++ checkdead() unlock(&sched.lock) lasttrace := int64(0) idle := 0 // how many cycles in succession we had not wokeup somebody delay := uint32(0) for { if idle == 0 { // start with 20us sleep... delay = 20 } else if idle > 50 { // start doubling the sleep after 1ms... delay *= 2 } if delay > 10*1000 { // up to 10ms delay = 10 * 1000 } usleep(delay) now := nanotime() next, _ := timeSleepUntil() ... if atomic.Load(&scavenge.sysmonWake) != 0 { // Kick the scavenger awake if someone requested it. wakeScavenger() } // retake P's blocked in syscalls // and preempt long running G's if retake(now) != 0 { idle = 0 } else { idle++ } // check if we need to force a GC if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 { lock(&forcegc.lock) forcegc.idle = 0 var list gList list.push(forcegc.g) injectglist(&list) unlock(&forcegc.lock) } ... unlock(&sched.sysmonlock) }}复制代码go func(){}之后
ppen.jpeg)
go func(){}创建一个新的goroutineG保存在P的本地队列,如果本地队列满了,保存在全局队列G在M上运行,每个M绑定一个P。如果P的本地队列没有G,M会从其他P的本地队列,挥着G的全局队列,窃取G当M阻塞时,会将M从P解除。把G运行在其他空闲的M或者创建新的M。当M恢复时,会尝试获得一个空闲的P。如果没有P空闲,M会休眠,G会放到全局队列。