第97期 | 一万个连接,一个进程怎么扛?

操作系统 · · 九羲科技

第12章第三期(收束)。上一期跟一个包进了内核、看网络学会呼吸;这一期问并发的另一端:一万个连接同时敲门,一个进程怎么扛得住?从 1999 年的 C10K,到 2002 年的 epoll,再到今天 AI 推理的万流并发。

一、1999年,一万把椅子不够坐了

1999 年,Dan Kegel 写下《The C10K problem》,开篇就一句话:"It's time for web servers to handle ten thousand clients simultaneously, don't you think?"¹ 当时的机器:1000MHz、2GB 内存、千兆网卡,1200 美元一台。他算了一笔账:两万个客户端摊下来,每个只占 50KHz、100KB 内存、50Kbit/s——"硬件早不是瓶颈了。"¹ 而 1999 年的 cdrom.com,已经用一块千兆网卡同时扛住了一万个客户端¹。

瓶颈在软件。老路有三条:一个连接 fork 一个进程(1980 年代的经典做法,内存先扛不住);select()——名字早进了标准库,可 FD_SETSIZE 把上限钉死在 1024¹;poll()——没有硬上限,但 Kegel 写得很直白:"几千个 fd 就开始慢",因为每次调用都要把名单全拷进内核、逐个扫描¹。

反常识的细节在分工里:select 的病不是"问得慢",是"每次都要重新问"。2002 年 11 月 19 日,Davide Libenzi 在 LKML 贴出 epoll 最终版接口²,合入 2.5.44³。哲学整个翻了过来:select 是用户反复问内核"谁有动静",epoll 是内核替用户记着名单、有动静才上报。兴趣列表 epoll_ctl 登记一次;有包到,内核直接把"动了的 fd"推进就绪队列;epoll_wait 睡着等,醒来只拿动了的——返回的是事件,不是名单,与总数无关³。man page 里那句"O(1) 扩展性",说的就是这个³。

水平触发、边缘触发这对术语,来自 Jonathon Lemon 2000 年在 BSDCON 讲 kqueue 的论文¹:电平触发是有事一直报,边缘触发只报"从无到有"的那一下。kqueue(FreeBSD,2000)、Solaris 的 /dev/poll,几乎同时交出各自的答卷——不是一家之见,是时代共同的答案。

这是最地道的发展性研究:没发明新原理(事件驱动 select 早就有了),只把"每次问一遍"工程化成"登记一次"。杨振宁说的中间层胜利,在这里又赢一次。

select/poll 与 epoll 的对照:前者每次把名单复印进内核逐个问,后者登记一次、有事才报。内核替你记着名单,是 epoll 全部的魔法。

二、AI 的并发,是长连接的海

如果 AI 的操作系统先出现,它大概不会再问"一万个连接谁有动静"——因为它每天面对的,就是这道题。

2026 年 10 月 29 日,DeepSeek 开源内部推理框架 DeepInfer:单集群一天 155 万亿 token 吞吐⁴。数字背后是另一个 C10K:每一次流式输出,都是一条 hanging 住的长连接——AI 时代的并发,不是"一万个网页请求",是"一万条同时吐 token 的流"。

而这些流的底下,跑的还是 2002 年的东西。推理服务(vLLM、SGLang)的异步服务层站在事件循环上;Python 的 asyncio 在 Linux 上,默认选择器就是 EpollSelector——本机一行命令可验(见③)。二十四年前 Libenzi 写下的接口,此刻正在给每一个 token 让路。

推演一步。epoll 回答的是"一万个字节流里谁有动静";原生 AI OS 要问的会再高一层:"一万条意图流里,谁卡住了、谁在等工具返回、谁该被抢占"。事件从"fd 可读"升级为"agent 状态变迁"——这是 epoll 哲学的后代,不是 epoll 本人。

言外之物在这里:Kegel 当年算的是"每个客户端 50KHz、100KB",结论是瓶颈全在软件怎么写。今天 AI 推理押韵了:算力不是瓶颈,瓶颈在"怎么把一万条流喂饱"——连续批处理、KV cache 分页。PagedAttention 把 OS 的分页思想用进 KV cache(SOSP 2023)⁵,赢的还是组织方式。瓶颈永远从硬件移到软件,再从软件移到组织方式——Linux 赢在组织方式,epoll 赢在组织方式,AI 推理赢的也是组织方式。

范式之眼再看一层:事件循环仍是"一个线程顺序处理事件"的线性后代,而算力主体是向量阵列。下一步的并发抽象,也许不是"更快地问谁有事",而是"批量地、向量化地处理一万个同时到来的事件"。io_uring 已经在路上了——第 31 章再讲。

三、手边任何一台 Linux 机器,今晚就能动手:

先看血统:python3 -c "import selectors; print(selectors.DefaultSelector)"——吐出来的是 EpollSelector:这台机器的事件循环,底下是 2002 年的 epoll。

再看"登记一次"的证据:跑一个 Python asyncio 程序,另开终端 strace -e trace=epoll_wait -p <pid>——事件循环大部分时间都在 epoll_wait 里睡着,有事才醒。这就是"门口挂铃"的现场。

小实验:写两个 40 行的 echo server,一个用 select 循环、一个用 selectors.DefaultSelector(Linux 上自动走 epoll),wrk 压 2000 并发,看 CPU 曲线的分叉——C10K 当年问的,就是这一道题。

这套手感可以直接搬去量企业私有化 AI 落地现场的推理服务:并发长连接数、流式输出的抖动,先看事件循环配没配对,再看 GPU 利用率。定位瓶颈的顺序永远是:先 IO,再算力。

觉 然 小 注

select 是挨家敲门问"谁在家",epoll 是门口挂个铃——有事你摇铃,没事别敲门。2002 年敢把用了二十年的范式翻过来,是出生勇气;相信"不问"比"问"更接近真相,是相信相信的力量。老子说"道常无为而无不为"⁶——有时最好的调度,就是不调度。

引 用 与 出 处

  1. [1] Dan Kegel. The C10K problem. 1999. http://www.kegel.com/c10k.html(一手核对:开篇句、cdrom.com 万并发、select FD_SETSIZE、poll 数千变慢、kqueue/BSDCON 2000)
  2. [2] Davide Libenzi. [rfc] epoll final (hopefully) interface. LKML, 2002-11-19. https://lwn.net/Articles/16026/(一手核对:日期、作者、2.5.44)
  3. [3] epoll(7), Linux Programmer's Manual. https://manpages.ubuntu.com/manpages/jammy/man7/epoll.7.html(一手核对:2.5.44 引入、边沿/水平触发、O(1) 扩展性)
  4. [4] 澎湃新闻(经"大模型与智能体前沿"日报 2026-10-30 转引):DeepSeek 开源内部推理框架 DeepInfer,单集群一天 155 万亿 token 吞吐. 2026-10-29.(二手转引)
  5. [5] Woosuk Kwon et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. SOSP 2023. arXiv:2305.19064.(思想化用)
  6. [6] 《道德经》第三十七章,王弼注本.(思想化用)

· · ·

← 返回资讯中心