Engineering · 2 min read
GMP 排程器與 netpoller:I/O 等待不佔 CPU
一個 OS thread 等上千個 socket,每個等待者只是 ~2KB 的 goroutine。GMP 排程器怎麼 park 和 wake I/O-bound 的 goroutine。
一個 OS thread 等上千個 socket;每個等待者只是一個 ~2KB 的 goroutine。
GMP 模型
- G = goroutine(~2KB),M = OS thread(~1MB),P = logical processor(數量 =
GOMAXPROCS)。G 必須綁定 P + M 才能執行,同時最多GOMAXPROCS個 goroutine 在跑 Go 程式碼。 - Park:goroutine 讀 socket 碰到 EAGAIN → runtime 呼叫
gopark,把 G 設為_Gwaiting,fd 註冊到 epoll,釋放 P/M。Parked G 佔 0 CPU。 - Wake:socket 就緒 → epoll 回傳 fd → runtime 把 fd 映射回 G →
goready把 G 標為_Grunnable→ P/M 從Read()之後繼續執行。
在這個 OOM 裡的角色
截圖服務的每個 /capture goroutine 有 ~99% 時間 parked 在瀏覽器的 websocket 上。GOMAXPROCS=1 時,19 個 goroutine 同時 in-flight(各自開一個瀏覽器分頁),只有 1 個在「執行」,但 19 個分頁的記憶體全部存在(GOMAXPROCS 與並發)。
I/O-parked 不代表不存在。goroutine 不佔 CPU,但它開的瀏覽器分頁佔記憶體。
參考來源:
- https://internals-for-interns.com/posts/go-netpoller/
- https://nghiant3223.github.io/2025/04/15/go-scheduler.html
- https://goperf.dev/02-networking/networking-internals/
- https://man7.org/linux/man-pages/man7/epoll.7.html
Related: 參見GOMAXPROCS 限制平行不限並發、用 TryAcquire 做 load shedding,或回到系列總覽。