非同步與高效能輸入輸出

C10k(與 C10M)問題

/ C10k -> see-ten-KAY; C10M -> see-ten-EM /

C10k 問題是大約 1999 年首次提出的著名挑戰:一台伺服器如何能同時處理一萬條網路連線?「C」代表並行連線(concurrent),「10k」代表一萬。當時最直覺的做法——一條連線一個執行緒或行程——在那個規模就崩潰了,這個問題迫使人們重新思考伺服器該怎麼蓋。C10M 是現代、艱難得多的重述:一千「萬」條並行連線。

為何「一連線一執行緒」會崩潰:每個執行緒都需要自己的堆疊(常見約一 MiB 的定址空間),核心排程器必須追蹤並在數千個之間做上下文切換,而其中大多只是閒著等 I/O——於是你為一群什麼都沒做的執行緒付出龐大的記憶體與切換代價。C10k 的答案是把設計反轉過來:用非阻塞描述符加上可擴展的就緒機制(epoll、kqueue)與事件迴圈,於是「一個」執行緒、或一個與 CPU 核心數相匹配的小執行緒池,就能驅動一萬條連線,只把 CPU 花在任一瞬間真正活躍的那寥寥幾條上。C10M 把門檻拉到連核心的每封包與每系統呼叫的負擔都成為瓶頸,逼出像 SO_REUSEPORT 分片、細緻的 NUMA 放置、零複製路徑、甚至繞過核心的使用者空間網路(DPDK)等技術。

為何重要:C10k 正是整套非同步 I/O 工具箱存在的原因——epoll、kqueue、IOCP、io_uring、事件迴圈與現代非同步執行時,全是被它形塑的。理解它,便解釋了高效能伺服器「為何」避開阻塞呼叫與「一連線一執行緒」的設計。誠實的細微之處:一萬條閒置連線與一萬條忙碌連線是非常不同的問題;C10k 主要關乎「便宜地撐住許多大多閒置的連線」,而服務一萬條真正活躍的連線,則受限於純 CPU、記憶體頻寬與網路。

/* 一連線一執行緒:10000 條連線 ~ 10000 個堆疊 ~ 10 GiB 堆疊定址空間 + 一萬路排程 */ /* 事件迴圈: 10000 條連線 ~ 1 個執行緒 + 1 個 epoll 集合,CPU 只花在活躍的少數上 */

同樣的一萬條連線,依設計不同,所耗資源天差地別。

C10k 是個擴展/架構問題,不是單一 API。現代作業系統用 epoll/kqueue 幾乎輕而易舉就能達到 C10k;更難、仍在進行的前沿是 C10M,那裡每封包的核心負擔成為主宰,人們轉向分片、零複製與核心繞過。撐住閒置連線很便宜;對它們全部做真正的工作則不然。

又称
ten thousand concurrent connectionsC10k 問題(一萬條並行連線)