Msysmondaemonsysmongo tool trace
sysmon20us~10msGoroutine
// src/runtime/proc.go // forcegcperiod is the maximum time in nanoseconds between garbage // collections. If we go this long without a garbage collection, one // is forced to run. // // This is a variable for testing purposes. It normally doesn't change. var forcegcperiod int64 = 2 * 60 * 1e9 // Always runs without a P, so write barriers are not allowed. // //go:nowritebarrierrec func sysmon() { ...... 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) ...... } ...... }
sysmon()
sysmon工作职责
Ggo runtime schedulerGGP
Gsysmon
网络轮询器监控
网络请求G网络轮询器(NetPoller)Pgoroutinesysmon
sysmon20us~10ms网络轮询器Gruntime.netpoll10msinjectglist()全局运行队列sysmonPfindrunnable() injectglist()
netpoll()injectglist()netpoll()
// src/runtime/proc.go func sysmon() { ...... for { ...... // poll network if not polled for more than 10ms lastpoll := int64(atomic.Load64(&sched.lastpoll)) if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now { atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now)) list := netpoll(0) // non-blocking - returns list of goroutines if !list.empty() { // Need to decrement number of idle locked M's // (pretending that one more is running) before injectglist. // Otherwise it can lead to the following situation: // injectglist grabs all P's but before it starts M's to run the P's, // another M returns from syscall, finishes running its G, // observes that there is no work to do and no other running M's // and reports deadlock. incidlelocked(-1) injectglist(&list) incidlelocked(1) } } ...... } ...... }
系统调用syscall监控
系统调用_Psyscall
// src/runtime/proc.go func sysmon() { ...... for { ...... // retake P's blocked in syscalls // and preempt long running G's if retake(now) != 0 { idle = 0 } else { idle++ } ...... } ...... }
syscall_Psyscall
// forcePreemptNS is the time slice given to a G before it is // preempted. const forcePreemptNS = 10 * 1000 * 1000 // 10ms func retake(now int64) uint32 { n := 0 // 全局p锁 lock(&allpLock) // 遍历每个P for i := 0; i < len(allp); i++ { _p_ := allp[i] // 1. p == nil 说明通过runtime.GOMAXPROCS 动态对P进行了调大,但当前还未初始化使用 直接跳过 if _p_ == nil { // This can happen if procresize has grown // allp but not yet created new Ps. continue } // pd := &_p_.sysmontick s := _p_.status sysretake := false // 2. P的状态为 _Prunning或_Psyscall的情况 if s == _Prunning || s == _Psyscall { // Preempt G if it's running for too long. // 运行时间过长 t := int64(_p_.schedtick) // 如果 p.sysmontick.schedtick != p.schedtick,则进行次数同步,并指定调度时间为now if int64(pd.schedtick) != t { pd.schedtick = uint32(t) pd.schedwhen = now } else if pd.schedwhen+forcePreemptNS <= now { // 如果 p上次调度时间 + 10ms <= now,说明下次调度的时候有点长,需要立即进行调度 // 但是当前P 是_Psyscall 状态的话,并不会抢占P,因为这种情况下M并没有与P绑定 preemptone(_p_) // In case of syscall, preemptone() doesn't // work, because there is no M wired to P. sysretake = true } } // 3. 系统调用 if s == _Psyscall { // Retake P from syscall if it's there for more than 1 sysmon tick (at least 20us). t := int64(_p_.syscalltick) if !sysretake && int64(pd.syscalltick) != t { pd.syscalltick = uint32(t) pd.syscallwhen = now continue } // On the one hand we don't want to retake Ps if there is no other work to do, // but on the other hand we want to retake them eventually // because they can prevent the sysmon thread from deep sleep. if runqempty(_p_) && atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0 && pd.syscallwhen+10*1000*1000 > now { continue } // Drop allpLock so we can take sched.lock. unlock(&allpLock) // Need to decrement number of idle locked M's // (pretending that one more is running) before the CAS. // Otherwise the M from which we retake can exit the syscall, // increment nmidle and report deadlock. incidlelocked(-1) if atomic.Cas(&_p_.status, s, _Pidle) { if trace.enabled { traceGoSysBlock(_p_) traceProcStop(_p_) } n++ _p_.syscalltick++ handoffp(_p_) } incidlelocked(1) lock(&allpLock) } } // 全局p锁 unlock(&allpLock) return uint32(n) }
队此之外还有由于原子、互斥量或通道操作调用导致 Goroutine 阻塞或直接调用 sleep 导致的阻塞也会导致调度器对G进行切换。
垃圾回收
两分钟sysmon
// src/runtime/proc.go func sysmon() { ...... for { ...... // 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) } ...... } ...... }
其它
另外还有一些其它作用,如 Timers 的处理,如函数 timeSleepUntil() 。
参考资料