Linux 明明只給 2 CPU,為什麼 Go 還開 5 個 P?深入 Go Runtime 與 cgroup 的階層限制
Go Runtime × Linux cgroup v2 × GOMAXPROCS
Go 1.25 開始加入 container-aware GOMAXPROCS:在 Linux 上,Runtime 不再只看機器有多少 logical CPUs,也會把 cgroup 的 CPU bandwidth limit 納入預設 parallelism 的計算。直覺上,如果 Linux 將一個程式限制在約 2 CPU 的 throughput,Go 應該也會把預設 GOMAXPROCS 壓到 2。
但我在追查 cgroup 行為時遇到一個反直覺的情況:Linux kernel 確實只讓整棵 subtree 得到約 2 CPU 的平均 throughput,Go 1.25、1.26、1.27 卻都可以在同一棵 hierarchy 的 child 裡得到 GOMAXPROCS=5。
kernel view:
effective CPU throughput ≈ 2 CPUs
Go runtime view:
runtime.NumCPU() = 5
GOMAXPROCS = 5
問題不在於 Linux 沒有限制成功,也不是 GODEBUG 沒有打開。真正的差異在更底層:Linux 的 CPU bandwidth enforcement 是 hierarchical 的,而目前 Go Runtime 的 cgroup CPU-limit observation 只追蹤 process 所在的 leaf cgroup。
這不是要證明「Go 的 container support 整體是錯的」。Go 1.25 的 container-aware
GOMAXPROCS 解決了很實際的 production 問題;本文處理的是它目前一個已知的 observation boundary:visible ancestor 有更嚴格 quota,而 leaf 本身沒有 local limit 時,Kernel 與 Runtime 可能對「有效 CPU throughput」得到不同的答案。
- CPU quota 與 GOMAXPROCS 為什麼不是同一件事
cpu.max = 200000 100000到底代表什麼- 為什麼 child unlimited 仍逃不掉 parent quota
- 三個 Go 版本的最小 reproducer
- 如何確認 Kernel 真的有 enforce 2 CPU
- 一路追進 Go Runtime 的 leaf-only implementation
- 為什麼 dynamic update 也看不到 parent
- ancestor-aware PoC 與結果
- 真正困難的是 Runtime policy,而不是 walk parent
- 目前能證明與還不能證明的範圍
一、先拆開三個常被混在一起的概念
在看 cgroup 以前,先不要把下面三件事情當成同一個東西:
logical CPU count
CPU bandwidth / quota
GOMAXPROCS
Go Runtime scheduler 常用 G、M、P 來描述執行模型。非常簡化地看:
Goroutine (G)
│
▼
Processor (P)
│
▼
OS Thread (M)
│
▼
Linux scheduler
│
▼
CPU
GOMAXPROCS 控制的是 Go Runtime 願意提供多少「同時執行 Go code 的 parallelism」。如果 GOMAXPROCS=5,可以粗略理解為 Runtime 最多讓五個 P 同時承載正在執行的 Go code。
但 Linux cgroup 的 CPU quota 不是「同時最多只能用幾顆 CPU」的硬限制。Go 官方在介紹 container-aware GOMAXPROCS 時也特別把兩者分開:
GOMAXPROCS:parallelism limit。- cgroup CPU limit:一段 wall-clock period 內可消耗的 CPU-time throughput limit。
這個 distinction 是後面所有現象的起點。
二、cpu.max = 200000 100000 到底限制了什麼?
在 cgroup v2 中,cpu.max 的格式是:
$MAX $PERIOD
Linux kernel 文件對它的定義很直接:一個 group 在每個 $PERIOD 期間,最多可以消耗 $MAX 的 CPU time;max 則代表沒有這個 bandwidth 上限。
所以:
cpu.max = 200000 100000
可以讀成:
每 100,000 µs wall time
最多消耗 200,000 µs CPU time
這裡最容易誤會的是:它不是把 process pin 在兩顆特定 CPU,也不是規定任意時刻最多只能有兩條 thread running。
假設有五條 runnable threads,而且 affinity 真的允許它們分散到五顆 CPU,那麼理想化地看:
也就是大約 40 ms 就可能把這個 100 ms period 的整份 CPU budget 花完。剩下的時間,cgroup 可能遭到 throttling,直到新的 bandwidth period 補回 runtime。
0 ms 40 ms 100 ms
│────────────────────│─────────────────────────────────│
██████████████████████
5-way CPU burst
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
quota exhausted / throttled
所以當 effective CPU quota 大約只有 2,而 Go Runtime 卻允許 5-way parallelism 時,並不會憑空得到 5 CPU throughput。對 CPU-bound 工作而言,更可能發生的是:更快花完相同 quota,然後更早進入 Kernel throttling。
三、真正的關鍵:cgroup 是 hierarchy,不是彼此獨立的資料夾
接著進到這個問題最重要的 Linux 語義。
parent
└── child
└── process
如果 restrictive quota 設在 parent,child 本身即使沒有更嚴格的 local quota,也不代表 child 可以突破 parent。
Linux 的 CFS bandwidth 文件甚至直接列出一個 group 會被 throttle 的兩種情況:
- 自己的 quota 在該 period 內被耗盡;
- parent 的 quota 在該 period 內被耗盡。
第二種情況下,即使 child 自己理論上還有 runtime,也不能繼續執行,必須等 parent 的 runtime refresh。
所以:
parent cpu.max = 200000 100000
│
└── child no tighter local limit
│
└── process
對 Linux scheduler 而言,這個 process 所在 subtree 的 CPU throughput 並不是無限。ancestor 的 bandwidth control 仍然有效。
我實際建立的是 private cgroup v2 threaded hierarchy。限制放在 parent;process 位於下面的 threaded leaf,而該 leaf path 沒有可讀的 local
cpu.max。這個 topology 足以重現 Go 的 leaf-only observation boundary,但我不把它包裝成「所有可能的 domain hierarchy 都已完成外部 integration test」。後面會把 source-level limitation 與實際跑過的 topology 分開說。
四、最小 reproducer:Kernel 有 2 CPU ancestor,Go leaf 得到 5
測試環境是 Debian 13、Linux 6.18.35、host cgroup v2。外層 container 自己的 cpu.max 是 4 CPU,CPU affinity / runtime.NumCPU() 可見 5 CPUs。為了避免外層 read-only cgroup 干擾,我在 unprivileged user + cgroup namespace 中建立 private writable cgroup v2 hierarchy。
核心 topology:
visible private cgroup v2 mount
└── parent
├── cpu.max = 200000 100000 # ≈ 2 CPUs
│
└── leaf
├── local cpu.max = <absent>
│
└── Go process
先把 Go process 直接放在 restrictive parent:
===== direct-parent =====
parent_cpu_max=200000 100000
runtime=go1.27.0
num_cpu=5
gomaxprocs=2
這很合理:Go 看到 process 自己所在 cgroup 的 limit,因此預設成 2。
接著只多一層 leaf,process 改放進 leaf:
===== unbounded-child =====
parent_cpu_max=200000 100000
leaf_cpu_max=<absent>
runtime=go1.27.0
num_cpu=5
gomaxprocs=5
Linux hierarchy 沒變成 unlimited,但 Go 的答案變成 5。
三個 stock release 都一樣
| Go release | Process 直接在 2-CPU parent | Process 在 child leaf | leaf + 強制 container GODEBUG |
|---|---|---|---|
| Go 1.25.12 | 2 | 5 | 5 |
| Go 1.26.5 | 2 | 5 | 5 |
| Go 1.27.0 | 2 | 5 | 5 |
也就是說,至少在這組 topology 下,這不是 Go 1.27 才突然出現的 regression,而是 Go 1.25 引入 container-aware default 後一路延續到 Go 1.27 的 implementation boundary。
container-aware 預設還受到 module 的 Go language version 影響。Go 官方的 GODEBUG 文件指出,language version 1.24 及以前預設相當於
containermaxprocs=0。我的 raw matrix 也重現:Go 1.27 binary 搭配 go 1.24 時,即使 process 直接位於 2-CPU parent,預設仍可得到 5;明確設 GODEBUG=containermaxprocs=1 後才變成 2。本文主要矩陣使用啟用 Go 1.25 container behavior 的設定。
五、先做反證:會不會只是檔案寫 2 CPU,但 Kernel 根本沒有 enforce?
只看 cpu.max 不夠。要說 ancestor quota 真的是「有效限制」,還需要 Kernel accounting。
我在 child leaf 裡跑 busy workers。以 Go 1.27 stock leaf-default 的一次 raw result 為例:
wall time ≈ 2.021 s
usage_usec delta = 4,120,021 µs
nr_throttled delta = 20
throttled_usec delta = 5,988,993 µs
GOMAXPROCS = 5
換句話說,約 2.02 秒 wall time 裡,整棵 subtree 總共取得約 4.12 CPU-seconds:
這和 parent 的 2-CPU throughput limit 非常接近,而且 nr_throttled 增加約 20 次,也和約 100 ms 一個 enforcement period 的量級吻合。
相對地,同一版 Go process 直接位於 parent、預設 GOMAXPROCS=2 時:
wall time ≈ 2.017 s
usage_usec delta = 4,031,230 µs
nr_throttled delta = 4
throttled_usec delta = 9,686 µs
GOMAXPROCS = 2
兩邊 throughput 都受到約 2 CPU 的 parent bandwidth 約束,但 leaf case 因為 Go 開到 5-way parallelism,throttling counter 明顯更高。
throttled_usec:上面的約 5.99M µs 不應直接翻譯成「process 在 2 秒 wall time 裡被停了 5.99 秒」。這類 cgroup throttling 統計可能聚合多個 execution entities / CPUs 的 throttled time。本文拿它當「throttling 確實大量發生」的 supporting signal;最直觀的 throughput ground truth 仍是
usage_usec / wall time ≈ 2 CPUs,再搭配 nr_throttled。
到這裡,可以把兩個事實分開:
Kernel:
visible ancestor quota 確實限制整棵 subtree ≈ 2 CPUs
Stock Go:
process 位於沒有 local limit 的 leaf
→ 預設 GOMAXPROCS = affinity CPU count = 5
六、追進 Go Runtime:答案其實直接寫在 source comment 裡
接下來不猜,直接看目前 Go Runtime。
Go 的 cgroup-aware GOMAXPROCS 初始化會呼叫 internal/runtime/cgroup.OpenCPU。Runtime 在 startup 讀取 /proc/self/cgroup 與 /proc/self/mountinfo,找到 process 當下所在的 CPU cgroup,開啟對應的 CPU limit file,然後把 FD 留著。
Go 1.27 的 runtime/cgroup_linux.go 註解更直接承認兩個限制:
- 目前只讀真正包含 process 的 leaf cgroup;parent 可能有更嚴格、也才是 effective 的 limit。
- 如果 process 執行期間被搬到另一個 cgroup,目前也不會發現,因為所在 cgroup 只在 startup 檢查一次。
往下一層看 internal/runtime/cgroup,目前 CPU state 的核心概念是保存 quota FD;cgroup v1 另外保存 period FD,cgroup v2 則主要保存 cpu.max 的 FD。
把 implementation 簡化成概念圖,大致是:
/proc/self/cgroup
│
├── 找 process 當下所在 cgroup
│
/proc/self/mountinfo
│
└── 找 cgroup mount
│
▼
leaf CPU limit file
│
open()
│
▼
retain FD
│
▼
periodic pread(fd, 0)
這個設計有很明顯的優點:startup 找一次,後面 dynamic update 非常便宜。對同一個已開啟的 FD 從 offset 0 重新 pread,就能看到該檔案內容的更新。
但它也自然建立了一個 observation boundary:
parent/cpu.max = 2 CPUs
│
│ ← 沒有被加入目前 retained state
▼
leaf
│
└── Go process
│
▼
retained leaf FD only
因此真正的問題不是「Go 不懂 cgroup」,而是:
Linux enforcement 的有效範圍可以沿 ancestor hierarchy 傳遞;Go Runtime 現在保存的 CPU-limit observation state 則是 leaf-oriented。
七、為什麼 GODEBUG=containermaxprocs=1 也修不好?
我也測了:
GODEBUG=containermaxprocs=1,updatemaxprocs=1
Go 1.25.12、1.26.5、1.27.0 在 child leaf 下仍然都是:
GOMAXPROCS=5
這不是 GODEBUG 失效。containermaxprocs=1 的作用是「啟用 cgroup CPU limit 對預設 GOMAXPROCS 的影響」,它沒有重新定義 cgroup scanner 的觀察範圍。
所以:
containermaxprocs=1
│
▼
啟用 container-aware algorithm
│
▼
仍然是目前的 leaf-only observation
Switch 能開啟功能,但不能把 leaf-only implementation 自動變成 ancestor-aware implementation。
八、Dynamic update 反而把這個 design boundary 證得更漂亮
Go 1.25 之後不只 startup 會算一次 default GOMAXPROCS,Runtime 也會週期性重新檢查 cgroup quota 的變化。
所以我讓 process 保持執行,然後把同一個 parent 的 quota 從:
200000 100000 → 300000 100000
≈ 2 CPUs ≈ 3 CPUs
如果 stock Go 1.27 process 直接位於 parent:
2 2 2 2 2 → 3 3 3 3 3
Runtime 能更新。因為它 retained 的正好就是 parent 的 CPU limit FD。
但 process 位於 child leaf 時:
5 5 5 5 5 → 5 5 5 5 5
parent 明明從 2 CPU 變成 3 CPU,Go 完全沒有反應。
這和 source model 精準對上:
direct-parent:
retained FD ──► parent/cpu.max
│
├── 2 CPUs
└── 3 CPUs
│
pread()
│
▼
Runtime sees 3
child-leaf:
retained state ──► leaf
parent 2 → 3 ───► 不在 observation set
所以這並不是「automatic update 壞掉」。
更精確的說法是:
Runtime 很忠實地更新了它決定要觀察的 state;只是 restrictive parent 從一開始就沒有進入那個 observation set。
九、我做了一個 visible-ancestor-aware Go 1.27 PoC
為了確認問題不是只能描述、不能實作,我對 Go 1.27 做了一份 proof of concept。
PoC 的策略很簡單:
Startup
- 找出 process 的 CPU cgroup。
- 找出 process 可見的 cgroup mount root。
- 從 leaf 往上走每個可見 ancestor,但不跨越 mount root。
- 打開每層可觀察的 CPU limit file,保留 FD。
visible mount root
│
▼
ancestor
│ cpu.max
▼
ancestor
│ cpu.max
▼
leaf
│
└── process
Periodic update
不再只讀一個 leaf FD,而是重新 pread 已保存的 visible ancestor limits,取其中最嚴格的 average quota / period。
limit[0] = pread(fd[0])
limit[1] = pread(fd[1])
limit[2] = pread(fd[2])
...
effective average CPU throughput
= minimum visible quota / period
本文主要實驗使用相同的 100 ms bandwidth period,因此取最嚴格
quota / period 能回答我們要的平均 CPU throughput。若不同 ancestor 使用不同 period,instantaneous throttling / refill interaction 會更複雜;我不把這個 PoC 描述成「完整精確模擬 Kernel 所有時間尺度行為」。
PoC 結果
| 測試 | 結果 |
|---|---|
| patched child leaf,parent = 2 CPU | 5/5 次得到 2 |
| stock child leaf,parent = 2 CPU | 5/5 次得到 5 |
| patched direct parent | 5/5 次得到 2 |
| patched child,parent 2 → 3 | 3/3 次自動更新到 3 |
| parent = 1.5 CPU | round up 到 2 |
| parent = 3.5 CPU | round up 到 4 |
containermaxprocs=0 | 維持 5 |
explicit GOMAXPROCS=3 | 維持 3 |
targeted runtime test 也通過:
ok runtime 4.165s
也就是說,ancestor-aware observation 至少能修正本文這組 visible-parent case,而且不需要破壞 explicit GOMAXPROCS、GODEBUG disable path 與既有 fractional rounding behavior。
十、Mount-root boundary:看得到才算,看不到不要猜
我另外測了一個我認為很重要的 boundary。
如果 restrictive parent 在 process 可見的 cgroup mount 裡:
visible mount root
└── parent 2 CPUs
└── leaf
└── process
結果:
stock = 5
patched = 2
但如果 cgroup namespace / mount root 把真正 restrictive 的 parent 藏在 process view 外面:
host-only restrictive parent 2 CPUs
└── [process-visible mount root]
└── leaf
└── process
結果:
stock = 5
patched = 5
我把這視為正確的 boundary,而不是 PoC 失敗。
Runtime 不應該越過自己可見的 cgroup mount,去猜 host namespace 上可能還存在什麼限制。比較合理的語義是:
visible ancestor:
可以納入 observation
hidden ancestor:
不猜、不越界
Go 最初的 container-aware GOMAXPROCS proposal 也曾討論過同一類現實問題:Container Runtime 常常只 mount 一部分 cgroupfs,因此 Runtime 未必能看到 host 上所有 parent。
十一、真正困難的不是「往上走」,而是 Runtime 要付什麼代價
做到這裡,很容易說:「那就把 PoC 送 upstream 啊。」
但我反而認為,correctness direction 已經不難,真正需要 maintainer 決定的是 resource policy。
PoC 採用的方案:startup 保留所有 visible ancestor FD
這個方案最大程度保留目前 Go 的設計哲學:
startup:
做 discovery / open
periodic update:
只做便宜的 pread
優點是 dynamic update 很便宜;缺點是 startup cost 與 FD 數量可能隨 visible hierarchy depth 增長。
Startup depth stress measurement
每個 timing trial 啟動 200 個 process,每個 depth 跑 5 次:
| visible hierarchy depth | stock median | patched median | 估算每 process 額外成本 |
|---|---|---|---|
| 1 | 0.19 s | 0.18 s | noise |
| 8 | 0.18 s | 0.17 s | noise |
| 32 | 0.18 s | 0.18 s | 無可測差異 |
| 128 | 0.19 s | 0.31 s | 約 0.6 ms |
| 512 | 0.37 s | 2.09 s | 約 8.6 ms |
prototype 的其他量測:
- BSS:增加 32,768 bytes。
- executable file:約增加 1.2 KiB。
- 本次典型 threaded test hierarchy:額外約 2 個 cgroup FDs。
- 一般 domain hierarchy 的 worst-case policy:可能變成每個 visible ancestor 一個 v2 FD,或 v1 的兩個 FD。
本次 threaded stress hierarchy 並不是「depth 512 就真的 retained 512 個有效
cpu.max FD」的實驗,因此這組數據主要量到的是 ancestor traversal、open / missing-file handling 與 setup cost。它能證明 O(depth) startup path 在極端深度會變貴,但不能單獨回答「每層都有有效 CPU controller file 的 domain hierarchy 會吃多少 FD」。那需要另外做 domain-hierarchy + RLIMIT_NOFILE 壓力測試。
還有一個 production failure mode:RLIMIT_NOFILE
如果正式設計真的選擇「retain every visible ancestor FD」,那就不能只問平均 Container 有幾層。
還要問:
如果 hierarchy 很深
或 process 已經接近 RLIMIT_NOFILE
某一層 open() → EMFILE
Runtime 要怎麼 fallback?
可能的政策包括:
- 保留已成功觀察到的部分 hierarchy;
- 整套 cgroup detection fallback;
- 設定 explicit ancestor-depth bound;
- 改成 update 時重新 walk/open/close;
- 只在 startup 納入 ancestor,之後不追 parent dynamic update。
這已經不是單純「多幾個 syscall」的問題,而是在定義:
當語言 Runtime 無法完整觀察 OS resource hierarchy 時,correctness 應該如何 graceful degradation?
幾個可能的 Runtime design
| 策略 | Startup | Periodic update | FD 成本 | Topology / migration |
|---|---|---|---|---|
| 保留所有 visible ancestor FDs | O(depth) | 便宜 | O(depth) | 不會自動發現新的 cgroup |
| 每次 update 重新 walk/open/close | 較低 | O(depth) | 低 | 較可能重新發現變化 |
| ancestor 只在 startup 納入 | O(depth) | leaf 很便宜 | 可控制 | parent dynamic change 看不到 |
| 設定 depth bound + fallback | bounded | bounded | bounded | 必須定義超過上限時的 semantics |
所以我不建議把目前 PoC 直接包裝成「production-ready fix」。它比較像一個 design probe:證明 ancestor-aware direction 能修正問題,同時把 startup、FD 與 lifetime observation trade-off 量化出來,讓 Go Runtime maintainers 決定哪個 policy 值得接受。
十二、目前證據到底到哪裡?
我希望把「已完成」與「還沒完成」分得很清楚。
已完成 / 有實際執行證據
- Go 1.25.12、1.26.5、1.27.0 stock matrix。
- visible ancestor + child leaf 的最小 reproducer。
- Kernel CPU usage / throttling accounting。
- stock dynamic quota 2 → 3 CPU。
- patched dynamic quota 2 → 3 CPU。
GODEBUGcontrols。- explicit
GOMAXPROCScontrol。 - fractional quota controls。
- visible mount-root boundary。
- patched Go repeated functional runs。
- targeted
go test runtime。 - patch dry-run against Go 1.27.0。
尚未完整完成
- full Go
all.bash:有開始重新 build toolchain,但超出當時 execution window,因此不能算 PASS。 internal/runtime/cgroupstandalone package test:部分 rebuilt toolchain 缺少相符的vetbinary,因此不算 PASS。- real cgroup v1 integration:當時 kernel environment 只有 v2;v1 還需要 upstream trybots 或真正的 v1 host。
- arbitrary process migration:PoC 沿用 startup discovery / retained FD 思路,沒有解決 process 被搬到另一個 cgroup 的問題。
- 不同 ancestor bandwidth periods 的完整時間語義:本文主要驗證 average throughput bound,不宣稱已覆蓋所有 refill interaction。
- 完整 domain hierarchy + FD exhaustion 測試:還需要補
RLIMIT_NOFILE與每層都有 controller file 的壓力測試。
目前這份研究 bundle 裡的 startup-depth raw/summary data 存在,但 public reproducer packaging 還應補齊它依賴的一個 helper script,才適合宣稱「從壓縮檔即可 end-to-end 重跑 startup benchmark」。這不影響核心 Go/cgroup 證據鏈,但如果我要把整包公開,我會先補掉再發布。
十三、所以這到底算不算 Go 的 bug?
我會很小心用字。
Go 1.27 現在的 source 已經明確註記 leaf-only observation 的限制,也知道更嚴格的 parent 才可能是 effective limit;最初的 container-aware GOMAXPROCS proposal 也討論過 cgroup hierarchy 與 Container Runtime 可能隱藏 parent 的問題。
因此,我不會把這寫成:
「Go 完全不知道 cgroup 是 hierarchical,
我發現了一個大家都不知道的大 bug。」
比較準確的是:
這是一個已知 implementation trade-off 所產生的可重現 observable mismatch:Kernel 根據 visible ancestor hierarchy enforce 更嚴格的 CPU bandwidth,但 Go Runtime 目前只把 leaf CPU limit 納入自己的 container-aware default observation。
是否要改變這個行為、以及要用什麼 lifetime observation policy 改,是 upstream design decision。
十四、我真正覺得有趣的,是 Runtime 應該「理解 OS 到什麼程度」
我一開始只是想回答一個看起來很簡單的問題:
Linux 明明只讓這棵 subtree 得到約 2 CPU throughput,為什麼 Go 還會開 5 個 P?
一路追下去,最後卻變成兩套 abstraction 的交界:
Linux kernel:
hierarchical CPU bandwidth enforcement
vs.
Go runtime:
available parallelism inference
Go 1.25 做了一件很重要的事:Runtime 不再完全假設「可見 logical CPUs = 應該使用的 parallelism」,而開始主動理解 Container 提供的 CPU resource control。
但一旦 Runtime 開始「理解」OS 的資源限制,問題就不再只是讀一個數字:
哪一層 cpu.max 才算?
ancestor 看得到嗎?
看不到的要猜嗎?
要追蹤多久?
process migration 怎麼辦?
要永久保留多少 FD?
hierarchy 太深怎麼 fallback?
不同 period 怎麼定義 effective limit?
這也是我目前對這個問題的結論:
對本文能看見 restrictive parent 的 topology,ancestor-aware observation 可以讓 Go 的 default
GOMAXPROCS 更接近 Linux 實際 enforce 的 average CPU throughput。真正還沒決定的是 architecture:
一個語言 Runtime 應該以什麼成本、在多長的生命週期裡,維持對整個可見 cgroup hierarchy 的觀察?
Linux 已經用 hierarchy 做了 enforcement。
接下來要決定的是:Runtime 應該看多深。
參考資料
- Go Blog, Container-aware GOMAXPROCS(2025-08-20): Go 官方對 parallelism limit、CPU throughput limit 與 throttling 的說明
- Go 1.25 Release Notes: Container-aware GOMAXPROCS 的正式 release 行為
-
Go GODEBUG documentation:
containermaxprocs與 Go language version 的預設 -
Go 1.27 Runtime source:
runtime/cgroup_linux.go -
Go internal cgroup source:
internal/runtime/cgroup/cgroup_linux.go - Go issue #73193: CPU limit-aware GOMAXPROCS default proposal / discussion
-
Linux Kernel documentation:
Control Group v2 —
cpu.max - Linux Kernel documentation: CFS Bandwidth Control — hierarchical throttling
- Go Blog, Go 1.27 is released(2026-08-19): Go 1.27 release announcement
實驗日期:2026-08-28。本文中的數值來自同一份 Go/cgroup investigation bundle 與其 raw summaries;PoC 是用來驗證 design direction 的 prototype,不代表 Go upstream 已接受或將採用此實作。


留言
發佈留言