Skip to content
All writing Part 03 of 11 · 從 19 個 goroutine 到 exit 137
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 模型

GOMAXPROCS = 1 P₀ (logical processor) M (OS thread) G₁ running ▶ netpoller (epoll): G₅ ⏸ G₆ ⏸ ... G₁₉ ⏸ waiting on socket 0 CPU / 0 P / 0 M 全部「in-flight」
  • 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() 之後繼續執行。
G: conn.Read(browser) socket empty → EAGAIN PARK gopark(G) → _Gwaiting fd 註冊到 epoll, 0 CPU P/M 釋放 → 跑其他 goroutine 瀏覽器回應 → kernel 標記 fd READY WAKE epoll_wait → ready fd 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,但它開的瀏覽器分頁佔記憶體。

參考來源:

Related: 參見GOMAXPROCS 限制平行不限並發用 TryAcquire 做 load shedding,或回到系列總覽

Tags #go #concurrency #reliability
// connect

Be brave | Be wise | Be grateful

21 BreakinCode

// elsewhere
LinkedInMedium (lang: en)Youtube
wh:~$William Hung· © 2026 Taipei · GMT+8 · Available for collaboration