runtime.NumCPU()仅返回逻辑CPU数量,与实时CPU占用率无关;真实CPU占用需通过操作系统接口(如/proc/stat)采集,推荐使用gopsutil的cpu.Percent()实现跨平台、阻塞1秒的准确采样。

为什么 runtime.NumCPU() 不能反映实时 CPU 占用率
很多人一上来就调用 runtime.NumCPU() 或 runtime.NumGoroutine(),误以为这是“CPU 使用率”。其实前者只返回逻辑 CPU 数量,后者是当前 goroutine 总数——和系统级 CPU 占用毫无关系。真实 CPU 占用必须读取操作系统暴露的统计接口,比如 Linux 的 /proc/stat,Windows 的 GetSystemTimes,macOS 的 host_processor_info。
直接手写跨平台采集既繁琐又易错,推荐用成熟库:gopsutil(github.com/shirou/gopsutil)。它封装了各平台底层调用,提供统一 API,且默认不启用 CGO(Linux/macOS 下纯 Go 实现,Windows 需 CGO)。
- 安装:
go get github.com/shirou/gopsutil/v3/cpu和github.com/shirou/gopsutil/v3/mem - 注意 v3 版本路径含
v3,旧版 v2 已归档,别导入错 - Linux 下无需额外依赖;Windows 编译需确保
CGO_ENABLED=1
用 cpu.Percent() 获取最近 1 秒平均 CPU 使用率
cpu.Percent() 默认阻塞 1 秒来对比两次采样值,这是计算使用率的最小合理时间窗口。跳过这个等待会得到无意义的瞬时快照(几乎总是 0% 或突刺)。
在 Gin handler 中直接调用没问题,但要注意并发安全:该函数内部已加锁,可被多个请求同时调用。
立即学习“go语言免费学习笔记(深入)”;
- 首次调用会初始化采样,耗时略高;后续调用稳定在 ~5ms 内(Linux 环境实测)
- 传入
time.Second是最小有效间隔,设更小(如100*time.Millisecond)会被自动 clamp 到 1 秒 - 想获取每核占用?传
true作为第二个参数:cpu.Percent(time.Second, true),返回[]float64
func cpuHandler(c *gin.Context) {
percents, _ := cpu.Percent(time.Second, false)
c.JSON(200, gin.H{"cpu_usage_percent": percents[0]})
}用 mem.VirtualMemory() 拿到真实内存占用指标
mem.VirtualMemory() 返回的是整个系统的物理内存统计,不是当前 Go 进程的 RSS。如果你要监控服务自身内存压力,得额外调用 runtime.ReadMemStats();但通常运维关心的是整机水位,所以用 VirtualMemory() 更合理。
关键字段:Total(总内存)、Available(可用内存)、UsedPercent(已用百分比)。注意 Used 是 Total - Available,不是 Used - Buffers - Cached(Linux 的 “真正已用” 定义),gopsutil 对各平台做了归一化处理,UsedPercent 可直接用于告警阈值判断。
-
Available在 Windows 上含义与 Linux 不同(Win 是 “当前可立即分配的”,Linux 是 “不含 active file cache 的剩余”),但UsedPercent值基本一致 - 避免反复调用:该函数每次执行都读取
/proc/meminfo(Linux)或调用系统 API,高频请求下建议加简单缓存(比如 2 秒 TTL) - 不要用
mem.SwapMemory()判断内存瓶颈——现代服务器大多禁用 swap,且 swap 使用率高 ≠ 内存不足
func memHandler(c *gin.Context) {
v, _ := mem.VirtualMemory()
c.JSON(200, gin.H{
"total_mb": v.Total / 1024 / 1024,
"used_mb": v.Used / 1024 / 1024,
"used_pct": v.UsedPercent,
"available_mb": v.Available / 1024 / 1024,
})
}Gin 路由注册与错误处理的常见疏漏
把 cpu.Percent() 或 mem.VirtualMemory() 放进 handler 里看似简单,但两个典型问题常被忽略:一是没检查 error,二是没设超时或限流。这些系统调用虽快,但在容器环境或负载极高时可能卡住(尤其 Windows 下偶尔有 API 超时)。
- 永远检查 error:比如磁盘满导致无法读
/proc、权限不足、CGO 调用失败,都会返回非 nil error - 给 handler 加 context 超时:
ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second),防止底层卡死拖垮整个 HTTP server - 别在 handler 里做多次采样求平均——Gin 是并发处理的,每个请求独立采样即可,聚合分析应交给 Prometheus 或前端做
- 如果部署在 Kubernetes,记得在 Pod 的
securityContext中开启privileged: false并挂载/proc(默认已挂载),否则 Linux 下读不到/proc/stat
最易被忽略的一点:gopsutil 的 CPU 采样依赖上一次结果,首次调用后若进程长期空闲,后续调用可能因内核计数器更新延迟而返回异常值(比如连续几次 0.0% 后突然跳到 99%)。生产环境建议启动时预热一次 cpu.Percent(time.Second, false),再正式提供接口。


















