前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >工作默默无闻的sysmon

工作默默无闻的sysmon

作者头像
LinkinStar
发布2022-09-01 14:50:24
2950
发布2022-09-01 14:50:24
举报
文章被收录于专栏:LinkinStar's BlogLinkinStar's Blog

sysmon 默默无闻的后台监控

golang 里面里面有一个默默无闻的工作者在后台跑着,它的名字叫 sysmon ,你可能在某个地方见到过它。我最早是在 gc 中第一次见到了它,当时只知道默认有一个两分钟的 gc 是由它来控制的,那么它究竟还做了什么工作呢?今天我们就来看看它。

启动

首先让我们来看看 sysmon 是谁启动的。

代码语言:javascript
复制
// The main goroutine.
func main() {
	...........
	if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
		// For runtime_syscall_doAllThreadsSyscall, we
		// register sysmon is not ready for the world to be
		// stopped.
		atomic.Store(&sched.sysmonStarting, 1)
		systemstack(func() {
			newm(sysmon, nil, -1)
		})
	}
  ............
  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()

注意哦,这个 main 不是我们写的 main,而是 runtime 的 main,go 启动的时候会调用这个 runtime.main ,而我们的写的 main 会在后面被调用,也就是 main.main 的时候被调用。而 newm 方法会创建一个 m,这里的 m 就是 gpm 模型中的 m,newm 调用 newm1 然后调用 mstart 启动

这里就有了第一个细节,sysmon 不需要一个 p 就能执行,只需要绑定一个 m 就能执行了

工作内容

检查死锁情况

代码语言:javascript
复制
func sysmon() {
	lock(&sched.lock)
	sched.nmsys++
	checkdead()

一上来的第一步就是检查有没有死锁的情况

工作时间调整

代码语言:javascript
复制
func sysmon() {
	lock(&sched.lock)
	sched.nmsys++
	checkdead()
	unlock(&sched.lock)

	// For syscall_runtime_doAllThreadsSyscall, sysmon is
	// sufficiently up to participate in fixups.
	atomic.Store(&sched.sysmonStarting, 0)

	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)
		mDoFixup()
  • 一开始 delay 是 20us,也就是说一开始就停 20us 就执行了
  • 然后如果循环了 50 次还是什么也没做,没有唤醒任何 goroutine 那么就开始倍增 delay
  • 最长 delay 时间为 10ms

简单的来说,如果发现经常没有工作可以做的话,它就会慢下来一点

再休息一会?

代码语言:javascript
复制
now := nanotime()
if debug.schedtrace <= 0 && (sched.gcwaiting != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs)) {
  lock(&sched.lock)
  if atomic.Load(&sched.gcwaiting) != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs) {
    syscallWake := false
    next, _ := timeSleepUntil()
    if next > now {
      atomic.Store(&sched.sysmonwait, 1)
      unlock(&sched.lock)
      // Make wake-up period small enough
      // for the sampling to be correct.
      sleep := forcegcperiod / 2
      if next-now < sleep {
        sleep = next - now
      }
      shouldRelax := sleep >= osRelaxMinNS
      if shouldRelax {
        osRelax(true)
      }
      syscallWake = notetsleep(&sched.sysmonnote, sleep)
      mDoFixup()
      if shouldRelax {
        osRelax(false)
      }
      lock(&sched.lock)
      atomic.Store(&sched.sysmonwait, 0)
      noteclear(&sched.sysmonnote)
    }
    if syscallWake {
      idle = 0
      delay = 20
    }
  }
  unlock(&sched.lock)
}

通过 timeSleepUntil 方法计算出下一个 timer 运行需要被唤醒的时间,如果没有需要触发的定时器,并且当前调度器需要 gc 或没有其他工作,那么就还能再休息一会,休息时间根据 forcegcperiod 来计算

网络工作

代码语言:javascript
复制
lock(&sched.sysmonlock)
// Update now in case we blocked on sysmonnote or spent a long time
// blocked on schedlock or sysmonlock above.
now = nanotime()

// trigger libc interceptors if needed
if *cgo_yield != nil {
  asmcgocall(*cgo_yield, nil)
}
// 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)
  }
}
mDoFixup()

lastpoll+10*1000*1000 < now 也就是说举例上一次网络轮训已经过去了 10ms 了,那么就需要检查是否有待执行的文件描述符。需要调用 netpoll 进行检查,如果检查到有,那么就通过 injectglist 将这些 goroutine 加入到全局队列中去。

抢占处理工作

代码语言:javascript
复制
// retake P's blocked in syscalls
// and preempt long running G's
if retake(now) != 0 {
  idle = 0
} else {
  idle++
}

retake 方法相信你应该不会陌生,这个方法处理了抢占的情况:一种是阻塞在了系统调用上的 P ,一种是运行时间过长的 G。这个时候就需要解绑 P 和 M,让其他的线程能获得 P 来继续执行其他的 G。具体里面的抢占的工作,让我们留到抢占的章节里面去,这里就不再赘述了。

gc 工作

代码语言:javascript
复制
// 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)
}

创建 gcTrigger 并调用 test 方法来确定是否需要 gc 如果需要的话就将执行 gc 的 g 直接 push 到全局队列中,然后调度执行

总结

sysmon 不需要 P 只需要 M 即可执行,能休息就休息,没工作时就慢下来,sysmon 主要做了以下几样工作:

  1. 检查死锁
  2. 检查网络轮训
  3. 检查抢占
  4. 检查 gc

总之其实 sysmon 其实在背后承担了一些检查的工作,来保证 runtime 的正常运行,同时也尽可能的减少它本身运行所需要的资源

参考链接

https://medium.com/@blanchon.vincent/go-sysmon-runtime-monitoring-cff9395060b5

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • sysmon 默默无闻的后台监控
    • 启动
      • 工作内容
        • 检查死锁情况
        • 工作时间调整
        • 再休息一会?
        • 网络工作
        • 抢占处理工作
        • gc 工作
      • 总结
        • 参考链接
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档