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」得到不同的答案。

一、先拆開三個常被混在一起的概念

在看 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 時也特別把兩者分開:

  • GOMAXPROCSparallelism 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
200 ms CPU time ÷ 100 ms wall time = 平均 2 CPUs

這裡最容易誤會的是:它不是把 process pin 在兩顆特定 CPU,也不是規定任意時刻最多只能有兩條 thread running。

假設有五條 runnable threads,而且 affinity 真的允許它們分散到五顆 CPU,那麼理想化地看:

5 CPUs × 40 ms = 200 ms CPU time

也就是大約 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 的兩種情況:

  1. 自己的 quota 在該 period 內被耗盡;
  2. 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 仍然有效。

本文 reproducer 的 scope:
我實際建立的是 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.12255
Go 1.26.5255
Go 1.27.0255

也就是說,至少在這組 topology 下,這不是 Go 1.27 才突然出現的 regression,而是 Go 1.25 引入 container-aware default 後一路延續到 Go 1.27 的 implementation boundary。

一個很容易漏掉的 Go 細節:
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:

4.12 CPU-s ÷ 2.02 s ≈ 2.04 CPUs

這和 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

  1. 找出 process 的 CPU cgroup。
  2. 找出 process 可見的 cgroup mount root。
  3. 從 leaf 往上走每個可見 ancestor,但不跨越 mount root。
  4. 打開每層可觀察的 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
這裡刻意用「average throughput bound」:
本文主要實驗使用相同的 100 ms bandwidth period,因此取最嚴格 quota / period 能回答我們要的平均 CPU throughput。若不同 ancestor 使用不同 period,instantaneous throttling / refill interaction 會更複雜;我不把這個 PoC 描述成「完整精確模擬 Kernel 所有時間尺度行為」。

PoC 結果

測試 結果
patched child leaf,parent = 2 CPU5/5 次得到 2
stock child leaf,parent = 2 CPU5/5 次得到 5
patched direct parent5/5 次得到 2
patched child,parent 2 → 33/3 次自動更新到 3
parent = 1.5 CPUround up 到 2
parent = 3.5 CPUround 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 額外成本
10.19 s0.18 snoise
80.18 s0.17 snoise
320.18 s0.18 s無可測差異
1280.19 s0.31 s約 0.6 ms
5120.37 s2.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。
不要過度解讀 depth benchmark:
本次 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。
  • GODEBUG controls。
  • explicit GOMAXPROCS control。
  • 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/cgroup standalone package test:部分 rebuilt toolchain 缺少相符的 vet binary,因此不算 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 的壓力測試。
另外一個 artifact 誠實性問題:
目前這份研究 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?

這也是我目前對這個問題的結論:

Correctness direction 已經相當清楚:
對本文能看見 restrictive parent 的 topology,ancestor-aware observation 可以讓 Go 的 default GOMAXPROCS 更接近 Linux 實際 enforce 的 average CPU throughput。

真正還沒決定的是 architecture:
一個語言 Runtime 應該以什麼成本、在多長的生命週期裡,維持對整個可見 cgroup hierarchy 的觀察?

Linux 已經用 hierarchy 做了 enforcement。

接下來要決定的是:Runtime 應該看多深。

參考資料

  1. Go Blog, Container-aware GOMAXPROCS(2025-08-20): Go 官方對 parallelism limit、CPU throughput limit 與 throttling 的說明
  2. Go 1.25 Release Notes: Container-aware GOMAXPROCS 的正式 release 行為
  3. Go GODEBUG documentation: containermaxprocs 與 Go language version 的預設
  4. Go 1.27 Runtime source: runtime/cgroup_linux.go
  5. Go internal cgroup source: internal/runtime/cgroup/cgroup_linux.go
  6. Go issue #73193: CPU limit-aware GOMAXPROCS default proposal / discussion
  7. Linux Kernel documentation: Control Group v2 — cpu.max
  8. Linux Kernel documentation: CFS Bandwidth Control — hierarchical throttling
  9. 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 已接受或將採用此實作。

留言

熱門文章