Skip to content

sysmon ​

阅读提示

  • 适用版本:Go 1.21+(示例在 Go 1.22 下验证)· 前置知识:GMP 调度器 · 阅读时长:约 10 分钟

sysmon是一个普通的函数,直译为系统监控,去掉注释部分也就 200 来行代码。它会在程序引导阶段时被分配一个单独的线程来启动,之后会在后台不断地监控 Go 程序运行时的状态,并作出相应的处理。关于它启动这部分的代码可以在runtime.main函数中查看到:

go
func main() {
    ...
  mp := getg().m
  mainStarted = true
  systemstack(func() {
        newm(sysmon, nil, -1)
    })
    ...
}

系统监控本身只是一个for循环,每一轮循环的间隔时间为 20 微秒,随着程序空闲指数的上升,间隔时间最多会提升至 10 毫秒。在每一轮循环中,它主要做了下面几件事情:

  • 辅助协程调度,抢占长时间运行的协程
  • 检查内存情况,并判断是否需要进行垃圾回收

完整示例 ​

下面的程序可以直接运行:复制到文件后执行 go run main.go,或粘贴到 Go Playground 运行。

go
package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	stop := make(chan struct{})

	// 一个忙碌的协程,只有 stop 关闭时才退出
	go func() {
		for {
			select {
			case <-stop:
				return
			default:
			}
		}
	}()

	// 即使有协程在忙,异步抢占也会让定时器正常触发
	time.Sleep(50 * time.Millisecond)
	close(stop)

	fmt.Println("timer fired while a goroutine was busy")
	fmt.Println("GOMAXPROCS >= 1:", runtime.GOMAXPROCS(0) >= 1)
}

预期输出:

text
timer fired while a goroutine was busy
GOMAXPROCS >= 1: true

常见错误与踩坑 ​

  • sysmon 是运行时内部协程。 无法直接调用它,理解它有助于判断暂停与抢占行为。
  • Go 1.14 起支持异步抢占。 没有函数调用的长 CPU 循环也能被信号中断。
  • 定时并不精确。 程序空闲时 sysmon 的轮询间隔会退避到约 10ms。
  • 阻塞系统调用会被监控。 线程在系统调用中卡住超过约 20µs,就可能触发新的 P 继续执行。
  • 空闲 2 分钟会触发强制 GC。 低流量服务也可能出现周期性 GC 记录,这是 sysmon 引起的。

更新记录 ​

  • 2026-10-14:新增"阅读提示"、完整可运行示例(含预期输出)、常见错误与踩坑清单,以及本更新记录。