
本文介绍在 go 中获取 cpu 拓扑(尤其是 linux 下的 numa 拓扑)的实用方法,涵盖原生系统接口解析、第三方库集成及注意事项,帮助开发者准确识别 cpu 核心、套接字、缓存层级与 numa 节点映射关系。
本文介绍在 go 中获取 cpu 拓扑(尤其是 linux 下的 numa 拓扑)的实用方法,涵盖原生系统接口解析、第三方库集成及注意事项,帮助开发者准确识别 cpu 核心、套接字、缓存层级与 numa 节点映射关系。
在高性能计算、低延迟服务或资源敏感型应用(如数据库、Kubernetes 设备插件、DPDK 绑核工具)中,精确掌握 CPU 拓扑结构至关重要——它不仅涉及逻辑核心(logical CPU)、物理核心(core)、超线程(SMT)、CPU 套接字(socket),更关键的是 NUMA(Non-Uniform Memory Access)节点归属。Go 语言标准库未直接提供跨平台拓扑查询能力,但可通过以下两种主流方式高效实现:
✅ 推荐方案:使用 gopsutil/cpu(生产就绪)
gopsutil 是最成熟、持续维护的 Go 系统监控库,其 cpu 子包已内置对 Linux /proc/cpuinfo 和 /sys/devices/system/node/ 的解析能力,可直接获取 NUMA 相关信息:
package main
import (
"fmt"
"log"
"github.com/shirou/gopsutil/cpu"
)
func main() {
// 获取所有逻辑 CPU 信息(含 topology 字段)
infos, err := cpu.Info()
if err != nil {
log.Fatal(err)
}
for _, info := range infos {
fmt.Printf("CPU %d: Socket=%d, Core=%d, Thread=%d, NUMANode=%d\n",
info.CPU, info.SocketID, info.CoreID, info.ThreadID, info.NumaNode)
// 注意:NumaNode 字段在 Linux 上由 /sys/devices/system/node/ 解析得出,Windows/macOS 返回 -1
}
}✅ 优势:自动适配内核版本差异;支持 socket/core/thread 层级识别;NUMA 节点 ID 可直接用于 numactl 绑核或内存分配策略;同时提供 cpu.Counts(logical) 等辅助函数。
⚙️ 底层原理:手动解析 /proc/cpuinfo 与 /sys(Linux 专用)
若需完全可控或极简依赖,可自行解析系统文件:
- /proc/cpuinfo 提供每个逻辑 CPU 的 physical id(socket)、core id、processor(逻辑索引);
- /sys/devices/system/node/ 下的 nodeX/cpulist 明确列出归属该 NUMA 节点的所有 CPU 列表(如 0-3,8-11)。
示例片段(NUMA 节点到 CPU 映射):
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func parseNUMACPUList(nodeDir string) ([]int, error) {
f, err := os.Open(nodeDir + "/cpulist")
if err != nil {
return nil, err
}
defer f.Close()
var cpus []int
scanner := bufio.NewScanner(f)
if scanner.Scan() {
for _, part := range strings.Split(scanner.Text(), ",") {
part = strings.TrimSpace(part)
if strings.Contains(part, "-") {
rangeParts := strings.Split(part, "-")
start, _ := strconv.Atoi(rangeParts[0])
end, _ := strconv.Atoi(rangeParts[1])
for i := start; i <= end; i++ {
cpus = append(cpus, i)
}
} else {
n, _ := strconv.Atoi(part)
cpus = append(cpus, n)
}
}
}
return cpus, nil
}
func main() {
nodes, _ := os.ReadDir("/sys/devices/system/node/")
for _, node := range nodes {
if strings.HasPrefix(node.Name(), "node") {
cpus, _ := parseNUMACPUList("/sys/devices/system/node/" + node.Name())
fmt.Printf("%s → CPUs: %v\n", node.Name(), cpus)
}
}
}⚠️ 注意事项与最佳实践
- 跨平台限制:NUMA 拓扑仅在 Linux(且启用 NUMA 支持的内核)和部分 BSD 系统上可靠;Windows/macOS 不暴露等价接口,gopsutil 在这些平台会将 NumaNode 设为 -1。
- 权限要求:读取 /sys/devices/system/node/ 通常无需 root,但某些安全加固系统(如 SELinux 强制策略)可能限制访问。
- 动态拓扑变化:热插拔 CPU 或 NUMA 配置变更时,需重新调用解析逻辑(不建议缓存过久)。
- 性能考量:gopsutil/cpu.Info() 是轻量级同步调用(毫秒级),适合初始化阶段使用;高频轮询应改用 /proc/stat + 差分统计。
✅ 总结
对于绝大多数 Go 项目,直接使用 github.com/shirou/gopsutil/cpu 是最稳妥、可维护的选择——它已封装了 NUMA 节点识别、CPU 层级建模与错误边界处理。仅当存在极端精简依赖、定制化解析逻辑(如结合 ACPI SRAT 表)或教学目的时,才建议手动解析 /proc 与 /sys。无论哪种方式,务必在目标部署环境(特别是容器中)验证 /proc/cpuinfo 和 /sys/devices/system/node/ 的可用性与完整性。










