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:新增"阅读提示"、完整可运行示例(含预期输出)、常见错误与踩坑清单,以及本更新记录。
