通过设定 GOMAXPROCS,用户可以调整调度器中 Processor(简称P)的数量。由于每个系统线程,必须要绑定 P ,P 才能把G交给 M 执行。所以 P 的数量会很大程度上影响 Go Runtime 的并发表现。GOMAXPROCS 在版本 1.5后的默认值是机器的 CPU 核数 (runtime.NumCPU)
设置GOMAXPROCS
package main
import ( "fmt" "runtime" )
funcmain() { n := runtime.NumCPU() // 获取机器的CPU核心数 fmt.Printf("NumCPU: %d\n", n) if n > 0 { runtime.GOMAXPROCS(n) // 设置GOMAXPROCS为CPU核心数 } }
容器内运行输出的是宿主机的CPU核心数
解决方案
go get go.uber.org/automaxprocs
运行Debug:
docker run -d --cpus=2 -v /Users/joohwan/GolandProjects/go-tools:/app golang:1.21.0 tail -f /dev/null
funcSet(opts ...Option) (func(), error) { cfg := &config{ // 获取CPU 资源配额的方法 procs: iruntime.CPUQuotaToGOMAXPROCS, roundQuotaFunc: iruntime.DefaultRoundFunc, // 最小为1 minGOMAXPROCS: 1, } for _, o := range opts { o.apply(cfg) }
undoNoop := func() { cfg.log("maxprocs: No GOMAXPROCS change to reset") }
// Honor the GOMAXPROCS environment variable if present. Otherwise, amend // `runtime.GOMAXPROCS()` with the current process' CPU quota if the OS is // Linux, and guarantee a minimum value of 1. The minimum guaranteed value // can be overridden using `maxprocs.Min()`. // 如果设置了环境变量GOMAXPROCS,就用这个 if max, exists := os.LookupEnv(_maxProcsKey); exists { cfg.log("maxprocs: Honoring GOMAXPROCS=%q as set in environment", max) return undoNoop, nil } // 获取CPU配额 maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS, cfg.roundQuotaFunc) if err != nil { return undoNoop, err } // 为设置CPU配额,直接返回 if status == iruntime.CPUQuotaUndefined { cfg.log("maxprocs: Leaving GOMAXPROCS=%v: CPU quota undefined", currentMaxProcs()) return undoNoop, nil } // 获取到当前的GOMAXPROCS prev := currentMaxProcs() undo := func() { cfg.log("maxprocs: Resetting GOMAXPROCS to %v", prev) runtime.GOMAXPROCS(prev) }
switch status { case iruntime.CPUQuotaMinUsed: cfg.log("maxprocs: Updating GOMAXPROCS=%v: using minimum allowed GOMAXPROCS", maxProcs) case iruntime.CPUQuotaUsed: cfg.log("maxprocs: Updating GOMAXPROCS=%v: determined from CPU quota", maxProcs) }
runtime.GOMAXPROCS(maxProcs) return undo, nil }
获取CPU 资源配额的方法
CPUQuotaToGOMAXPROCS
// 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. funcCPUQuotaToGOMAXPROCS(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 }
// NewCGroupsForCurrentProcess returns a new *CGroups instance for the current // process. funcNewCGroupsForCurrentProcess() (CGroups, error) { return NewCGroups(_procPathMountInfo, _procPathCGroup) }
如果上述两个字段任意一个未设置,方法返回 -1,表示不限制对 CPU 资源的使用,如果两个字段都设置了,使用下面的公式返回资源配额:
cpu.cfs_quota_us / cpu.cfs_period_us
// CPUQuota returns the CPU quota applied with the CPU cgroup controller. // It is a result of `cpu.cfs_quota_us / cpu.cfs_period_us`. If the value of // `cpu.cfs_quota_us` was not set (-1), the method returns `(-1, nil)`. func(cg CGroups) CPUQuota() (float64, bool, error) { cpuCGroup, exists := cg[_cgroupSubsysCPU] if !exists { return-1, false, nil }
cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam) if defined := cfsQuotaUs > 0; err != nil || !defined { return-1, defined, err }
cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam) if defined := cfsPeriodUs > 0; err != nil || !defined { return-1, defined, err }