Msysmondaemonsysmongo tool trace
sysmon
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
sysmon 工作职责

网络轮询器监控

网络请求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() 。

参考资料