返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

4.7.2 系统监控

发布于 2024-10-12 19:15:59 字数 5332 浏览 0 评论 0 收藏 0

监控(sysmon)自运行时启动时,就使用专门的线程运行。

  • 超过 2 分钟没有垃圾回收,强制执行。
  • 超 10 毫秒未网络轮询(netpoll),执行。
  • 向长时间运行 G 任务发出抢占调度。
  • 收回闲置超过 10 毫秒的 syscall P。
  • 检查定时器。
// proc.go

// The main goroutine.
func main() {
	if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
		systemstack(func() {
			newm(sysmon, nil, -1)
		})
	}
}
// forcegcperiod is the maximum time in nanoseconds between garbage
// collections. If we go this long without a garbage collection, one
// is forced to run.
var forcegcperiod int64 = 2 * 60 * 1e9


// Always runs without a P, so write barriers are not allowed.
func sysmon() {
    
	idle := 0           // 闲置(什么都没做)周期计数。
	delay := uint32(0)  // 循环间隔时间。

	for {
		if idle == 0 {        // 初始 20 微秒。
			delay = 20
		} else if idle > 50 { // 闲置超过 50 周期,加倍。
			delay *= 2
		}
		if delay > 10*1000 {  // 最高 10 毫秒。
			delay = 10 * 1000
		}
        
		usleep(delay)        
		now := nanotime()
        
        // STW,或没有 P 在工作,休眠。
		if debug.schedtrace <= 0 && (sched.gcwaiting != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs)) {
			if atomic.Load(&sched.gcwaiting) != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs) {
				syscallWake := false
                
                // 下个定时器触发时间。
				next, _ := timeSleepUntil()
                
                // 无需处理定时器,休眠。
				if next > now {
                    
                    // 休眠标志。
					atomic.Store(&sched.sysmonwait, 1)
                    
					// 休眠时间(短时间)。
					sleep := forcegcperiod / 2
					if next-now < sleep {
						sleep = next - now
					}
                    
                    // 休眠,等待唤醒或超时。
					syscallWake = notetsleep(&sched.sysmonnote, sleep)
					atomic.Store(&sched.sysmonwait, 0)
					noteclear(&sched.sysmonnote)
				}
                
                // 被 syscall 等主动唤醒,重置闲置和间隔。
                // 因为有新任务将执行。
				if syscallWake {
					idle = 0
					delay = 20
				}
			}
		}

		lock(&sched.sysmonlock)        
		now = nanotime()

		// 超过 10ms,轮询网络。
		lastpoll := int64(atomic.Load64(&sched.lastpoll))
		if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
            
            // 更新最后轮询时间。
			atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now))
            
            // 将轮询结果(Gs)加入全局队列。
			list := netpoll(0) // non-blocking - returns list of goroutines
			if !list.empty() {
				injectglist(&list)
			}
		}

        // 唤醒异步物理内存释放 G。
		if atomic.Load(&scavenge.sysmonWake) != 0 {
			wakeScavenger()
		}
        
		// 抢夺 syscall P,抢占调度长时间运行 G。
		if retake(now) != 0 {
			idle = 0
		} else {
			idle++
		}
        
		// 距上次垃圾回收已超过 2 分钟,强制执行。
		if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
			var list gList
			list.push(forcegc.g)
			injectglist(&list)
		}
        
		unlock(&sched.sysmonlock)
	}
}

通过比对前次检查记录和当前计数器,来决定是否采取措施。

// runtime2.go, proc.go

type p struct {
	schedtick   uint32     // incremented on every scheduler call
	syscalltick uint32     // incremented on every system call
	sysmontick  sysmontick // last tick observed by sysmon
}

type sysmontick struct {   // 保存最后一次检查记录。
	schedtick   uint32     // schedule/execute 执行次数。
	schedwhen   int64      // schedule/execute 检查更新时间。
	syscalltick uint32     // syscall 执行次数。
	syscallwhen int64      // syscall 检查更新时间。
}
// proc.go

// 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)
	for i := 0; i < len(allp); i++ {
        
		_p_ := allp[i]
		if _p_ == nil {
			// This can happen if procresize has grown
			// allp but not yet created new Ps.
			continue
		}
        
        // 上次检查记录。
		pd := &_p_.sysmontick
        
        // P 状态。
		s := _p_.status
        
		if s == _Prunning || s == _Psyscall {
            
			// 比较当前计数(p.schedtick)和上次记录(pd.schedtick)。
            // 不等,表示有新任务被执行,更新检查记录。
            // 相等,表示还在执行上次检查的任务。继续判断执行时间(两次检
            //      查间隔)是否超过 10ms,以此决定是否发出抢占调度信号。
            
			t := int64(_p_.schedtick)
			if int64(pd.schedtick) != t {
				pd.schedtick = uint32(t)
				pd.schedwhen = now
			} else if pd.schedwhen+forcePreemptNS <= now {
				preemptone(_p_)
				sysretake = true
			}
		}
        
		if s == _Psyscall {
            
            // 有新调用,更新检查记录。
			t := int64(_p_.syscalltick)
			if !sysretake && int64(pd.syscalltick) != t {
				pd.syscalltick = uint32(t)
				pd.syscallwhen = now
				continue
			}
            
            // 跳过: P 没额外任务 + 有其他自旋或闲置 P + 检查间隔少于 10 ms。
            // 上述任何一个条件不满足,都需要抢夺 P,去积极执行其他任务。
			if runqempty(_p_) && atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0 && pd.syscallwhen+10*1000*1000 > now {
				continue
			}
            
			unlock(&allpLock)
            
            // 修改状态,使得 syscallexit 无法继续使用该 P。(抢夺)
			if atomic.Cas(&_p_.status, s, _Pidle) {
				n++
				_p_.syscalltick++
                
                // 让 P 找个好人家,积极工作吧!奥利给!
				handoffp(_p_)
			}
            
			lock(&allpLock)
		}
	}
    
	unlock(&allpLock)
	return uint32(n)
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文