大家好,我是煎鱼。

在如今,云原生浪潮已经掀起了很多年,Kubernetes+Docker 的组合已经占据主流。Go 这一门编程语言,有一部分是借助云原生起家的。

不过 Go 在此一直有一个 “坑“,多年以来一直靠着第三方包来解决。最近终于有了一些新进展。

问题背景

在《runtime: make GOMAXPROCS cfs-aware on GOOS=linux[1]》中有明确到这个现象的表现是:

在 runtime.GOMAXPROCS() 中,其默认设置是采用操作系统可见的处理器数量。

因此可能存在与容器 CPU 配额(例如通过 Docker CFS 带宽控制实现的配额)存在严重数值偏差的情况。

这种情况可能导致程序出现显著的延迟异常,特别是在以下两类场景:

  1. 峰值负载期间

  2. 后台 GC 阶段占满所有处理器时

现实情况

在现实的生产使用中,很多同学是遇到问题后,要去排查 GC 等。才发现的这个 “坑”。

因此如果我们关心应用程序的性能延迟、可靠性等,正确的做法是通过设置:GOMAXPROCS=max(1,floor(cpu_quota)),永远不要超过你的 CPU 配额。

我们最常用的 uber-go/automaxprocs 中的实现 internal/runtime/cpu_quota_linux.go[2]

// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process
// to a valid GOMAXPROCS value. The quota is converted from float to int using round.
// If round == nil, DefaultRoundFunc is used.
func CPUQuotaToGOMAXPROCS(minValue int, round func(v float64) int) (int, CPUQuotaStatus, error) {
if round == nil {
  round = DefaultRoundFunc
 }
 cgroups, err := _newQueryer()
if err != nil {
return-1, CPUQuotaUndefined, err
 }

 quota, defined, err := cgroups.CPUQuota()
if !defined || err != nil {
return-1, CPUQuotaUndefined, err
 }

 maxProcs := round(quota)
if minValue > 0 && maxProcs < minValue {
return minValue, CPUQuotaMinUsed, nil
 }
return maxProcs, CPUQuotaUsed, nil
}

将此作为 GOMAXPROCS 的默认设置将让 Go 程序变得可靠。这也是大家的微服务应用都会用 uber-go/automaxprocs[3] 来做兜底的原因之一。

新提案

背景

在前几周 Go 团队成员 @Michael Pratt 提出了新的提案《proposal: runtime: CPU limit-aware GOMAXPROCS default[4]》:

该提案的目的是:希望修改 Linux 上的 Go runtime(运行时)机制,使用 CPU cgroup 配额限制(注意是:pod cpu limit 场景)来设置 GOMAXPROCS 的默认值。

以此解决我们前面提到在云原生场景下的问题。

实施方向

具体计划实施的方向如下:

  • 1、层级化优先级:

    • 物理机 → CPU 亲和性 → cgroup 限制,逐层收敛最终值。

    • 通过 min() 确保不超额使用资源(避免容器环境中的 CPU 节流)。

  • 2、cgroup 适配:

    • 对 cgroup v1/v2 的差异封装统一接口。

    • 遍历 cgroup 层级时需处理 cpu 子系统的挂载点(可能分布在/sys/fs/cgroup 的不同路径)

  • 3、自动更新机制:通过后台 goroutine 定期(如每 10 秒)检查以下指标并重新设置,伪代码如下:

func monitorCPULimit() {
    for {
        newLimit := calculateCPULimit()
        if newLimit != currentGOMAXPROCS {
            runtime.SetDefaultGOMAXPROCS()
        }
        time.Sleep(10 * time.Second)
    }
}
  • 4、兼容性保障:本次兼容性保障将由 GODEBUG cgroupgomaxprocs=1 控制。对于较旧的语言版本,默认值为 cgroupgomaxprocs=0。因此,只有在升级 Go 语言版本时,行为才会发生变化,而不是在升级 go tool 工具链时。

文档更新

本次变更后将会对现有的 GOMAXPROCS 有一定的改变,具体如下:

// GOMAXPROCS sets the maximum number of CPUs that can be executing
// simultaneously and returns the previous setting. If n < 1, it does not change
// the current setting.
//
// If the GOMAXPROCS environment variable is set to a positive whole number,
// GOMAXPROCS defaults to that value.
//
// Otherwise, the Go runtime selects an appropriate default value based on the
// number of logical CPUs on the machine, the process’s CPU affinity mask, and,
// on Linux, the process’s average CPU throughput limit based on cgroup CPU
// quota, if any.
//
// The Go runtime periodically updates the default value based on changes to
// the total logical CPU count, the CPU affinity mask, or cgroup quota. Setting
// a custom value with the GOMAXPROCS environment variable or by calling
// GOMAXPROCS disables automatic updates. The default value and automatic
// updates can be restored by calling [SetDefaultGOMAXPROCS].
//
// If GODEBUG=cgroupgomaxprocs=0 is set, GOMAXPROCS defaults to the value of
// [runtime.NumCPU] and does not perform automatic updating.
//
// The default GOMAXPROCS behavior may change as the scheduler improves.
func GOMAXPROCS(n int) int

// SetDefaultGOMAXPROCS updates the GOMAXPROCS setting to the runtime
// default, as described by [GOMAXPROCS], ignoring the GOMAXPROCS
// environment variable.
//
// SetDefaultGOMAXPROCS can be used to enable the default automatic updating
// GOMAXPROCS behavior if it has been disabled by the GOMAXPROCS
// environment variable or a prior call to [GOMAXPROCS], or to force an immediate
// update if the caller is aware of a change to the total logical CPU count, CPU
// affinity mask or cgroup quota.
func SetDefaultGOMAXPROCS()

总结

本提案作者计划如果该提案被接受,将会马上在 Go1.25 中实现和实施这份规划。一旦实施,对于 Go 开发者来讲是一个不错的消息。

因为即使在 2025 年的现在,即使 Go 最大客群之一是云原生。但这个问题本身仍然还没有被解决,大家还是靠 uber-go/automaxprocs 的库为主。

但需要注意,本次修改主要还是针对 pod limit 的场景,而不是 pod request 的场景。本质上还是不一样的。

参考资料

[1] 

runtime: make GOMAXPROCS cfs-aware on GOOS=linux: https://github.com/golang/go/issues/33803

[2] 

internal/runtime/cpu_quota_linux.go: https://github.com/uber-go/automaxprocs/blob/master/internal/runtime/cpu_quota_linux.go#L35

[3] 

uber-go/automaxprocs: https://github.com/uber-go/automaxprocs

[4] 

proposal: runtime: CPU limit-aware GOMAXPROCS default: https://github.com/golang/go/issues/73193

关注和加煎鱼微信,

一手消息和知识,拉你进技术交流群👇

你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路

日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!

原创不易 点赞支持

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐