首页 > 编程笔记 > Go语言笔记 阅读:8

Go语言sysmon监控线程的用法(附带实例)

Go语言中,当触发 gopark() 函数(用于将 G 挂起)、锁、通道、网络、垃圾回收、sleep 等相关操作时,G 的状态会从 _Grunning 变为 _Gwaiting,且会进入对应的定时器中等待。

每个 P 都有一个用于管理自己的定时器字段 p.timers,定时器中有对应的回调函数 checkTimers。每次调度 G 时都会执行该函数,检查并执行已经到时间的定时器。在到达指定的时间后调用这个回调函数,会让等待的 G 又恢复到 _Grunnable 状态,这时再将其重新放回到本地运行队列中。

但如果当前所有的 M 都处于运行状态,那么就可能存在不能及时触发调度的情况,最终会导致定时器不能被及时地执行。为了解决这个问题,Go 语言引入了监控线程 sysmon,它不与任何 P 绑定,直接由 M 执行。

监控线程 sysmon 也是由主协程创建的,其在 runtime 初始化之后,执行用户代码之前,由 runtime 启动。关键代码如下:
func main() {
   ...
   if GOARCH != "wasm" {
      systemstack(func() {
         newm(sysmon, nil, -1)
      })
   }
   ...
}

sysmon 用于监控系统的运行状态,并在出现意外情况时及时做出响应。由于它需要重复执行任务,因此始终处于运行状态。

在程序执行期间,监控线程 sysmon 每 20us~10ms 轮询一次,以监控长时间运行的 G。若发现有长时间运行的 G,就对其设置可被抢占的标识符,以便其他的 G 抢占执行。关键代码如下:
for {
   if idle == 0 {
        delay = 20
   } else if idle > 50 {
        delay *= 2
   }
   if delay > 10*1000 {
        delay = 10 * 1000
   }
   usleep(delay)
   ...
}

如果遇到网络请求导致运行阻塞的情况,调度器会将当前被阻塞的 G 放入网络轮询器中。这时,网络轮询器会执行异步网络系统调用,并让出 P,以便 P 能执行其他 G。

待网络轮询器完成异步网络调用后,将由监控线程 sysmon 将 G 切换回来并继续运行。关键代码如下:
for {
   ...
   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)
   if !list.empty() {
        incidlelocked(-1)
        injectglist(&list)
        incidlelocked(1)
   }
   }
   ...
   }
   ...
}

如果是系统调用引起的阻塞(P 的状态为 _Psyscall),则触发分离机制。当一个 P 处于 _Psyscall 状态时,调度器会把这个 P 从其所绑定的 M 上分离下来,并重新寻找其他空闲的 M 来执行相应的 G 代码。当系统调用返回时,调度器会再次把这个 P 绑定到一个空闲的 M 上,让它继续执行。

retake() 函数就是用来实现这个分离机制的。当调度器发现一个 P 的状态为 _Psyscall 时,就会调用 retake() 函数来重新获取这个 P,并把它绑定到一个空闲的 M 上执行。这样,就可以避免出现因系统调用阻塞导致的 G 无法执行的问题。关键代码如下:
for {
   ...
   if retake(now) != 0 {
      idle = 0
   } else {
      idle++
   }
   ...
}

如果垃圾回收器已经有两分钟没有运行了,则监控线程 sysmon 会通知 gchelper 协程强制执行一次垃圾回收。关键代码如下:
for {
   ...
   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)
   }
   ...
}

相关文章