想象你走进一所大学食堂。高峰时段,几千名学生同时涌进来,每个窗口都排着队。一种做法是:食堂安排一位聪明的服务员,他手里有个记事本,记下哪些桌的学生举手了——要加饭的去 3 号窗口,要加菜的去 7 号窗口。他不去打扰那些正安心吃饭的学生,只在有人举手时通知后厨。这就是 epoll 的智慧:事件驱动,按需通知。 另一种做法是:食堂在角落开了一个“批量出餐口”。学生把一叠点餐单一次性塞进一个小信箱,后厨按顺序做好后,把菜统一放在出餐台上,学生一次性端走。整个过程里,学生只需要跑一趟食堂,剩下的等待都在原地进行。这就是 io_uring 的智慧:批量提交、完成队列自取。 这两种“打饭模式”对应着两种改变高并发命运的技术。epoll 统治了互联网服务器十几年,而 io_uring 是内核为新时代高吞吐存储与网络准备的后起之秀。它们的核心差异藏在三个词里:系统调用开销、批处理能力与延迟特征。理解了这些,你就能在架构选型时做出清醒的判断。
掌握 epoll 与 io_uring 的取舍逻辑,是后端工程师、存储系统开发者做性能优化与架构选型的基本功,它直接决定了高并发系统的吞吐上限与 CPU 效率。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
系统调用开销
食堂打饭最累的不是排队,而是“跑腿”。你从自习室跑到打饭窗口,阿姨递出餐盘,你再端着跑回座位。这一来一回,路上的每一步都是纯消耗。在计算机世界里,这趟“跑腿”就是一次系统调用——应用程序从用户态切进内核态,请操作系统帮你完成一次文件读写。
更麻烦的是,read/write 这类传统调用每次都得跑一趟,数据还要从内核的“后厨”端到用户态的“餐盒”里,这就是内存拷贝。跑腿加端菜,两笔开销叠在一起。一次两次无所谓,每秒百万次呢?系统调用开销可以简洁建模:总开销等于调用次数乘以单次调用成本,再加上拷贝次数乘以单次拷贝成本。单次调用成本里藏着上下文切换——CPU 要保存当前任务状态、切换特权级、刷新缓存,这些都是一次“急刹车再重新加速”。epoll 通过事件通知减少了“空跑”,但每个就绪事件仍要发生一次系统调用去完成读写。
1. N_syscall(系统调用次数):应用程序“跑腿”的次数,每次读写或询问内核都算一次。
2. C_syscall(单次调用成本):包含上下文切换、特权级切换、缓存失效等固定开销,与操作内容无关,更像“起步价”。
3. N_copy 与 C_copy(拷贝次数与单次拷贝成本):数据从内核缓冲区搬到用户缓冲区的搬运费用,数据量越大越贵,相当于“端菜按距离计费”。
4. 求和:两个“跑腿费”相加,就是一次 IO 的完整账本,任何优化都是在削减其中一项。
所以衡量两种技术的第一把尺子就是“跑腿次数”。epoll 让服务员按需提醒,把无效跑腿砍掉了;io_uring 则更进一步,直接把一叠点餐单塞进信箱,让系统调用次数从“一单一次”变成“一叠一次”。少跑腿,是高性能 IO 的第一性原理。
事件驱动模型
那位聪明的服务员是怎么工作的?他站在大厅中央,记事本上记着所有需要关注的桌号。谁举手了,他就走过去问一句。没举手的桌,他一眼都不多看。这套“你叫我,我才动”的机制,就是事件驱动。
epoll 是事件驱动模型的代表作。应用程序先把关心的文件描述符注册进 epoll 实例,相当于告诉服务员“这几桌我要盯着”;然后调用 epoll_wait 挂起等待。内核发现有文件描述符就绪时,唤醒应用,并只告诉它哪些描述符可以读了。这就是“就绪通知”——服务员只在有人举手时递上名单。
epoll 的关键设计有三点:一是监听集合与就绪集合分离,复杂度从 select/poll 的 O(n) 降到 O(就绪数);二是水平触发(LT)——只要缓冲区还有数据就一直通知;三是边缘触发(ET)——只在状态变化瞬间通知一次。ET 更高效,但要求应用一次把数据读完,否则会丢事件。
1. E(就绪事件数):当前有多少桌举手了,它远小于注册的总桌数。
2. t_i(单事件处理时间):服务员服务一桌的时间,包含唤醒、一次读写系统调用与业务处理。
3. 累加求和:epoll 总耗时等于服务每个就绪事件的耗时相加,E 越小成本越低。
回到食堂:服务员绝不挨桌问“要不要加饭”,只服务举手的人。这就是 epoll 在“连接百万但大部分在发呆”的场景下依然省 CPU 的全部秘密——它把精力只花在有事情发生的时刻。
批量提交队列
聪明的服务员也有天花板:一次只能服务一桌。如果一百桌同时举手,他得跑一百趟。食堂经理想了个办法——为什么不让大家把点餐单集中起来,一次交给后厨呢?这就是批量。
io_uring 的核心是两块用户态与内核态共享的环形缓冲区:提交队列(SQ)与完成队列(CQ)。应用程序把请求描述(比如“读第 3 个扇区”)写进 SQ,然后调用一次 io_uring_enter,内核便会把 SQ 中待处理的一批请求一次性取走。SQ 是环形缓冲,写满或攒够一批就提交一次,像把一叠点餐单塞进信箱后按一次铃。
这个设计把“操作次数”和“系统调用次数”解耦了。你可以在用户态连续往 SQ 里塞几十个请求,再统一提交一次。请求越密集,摊薄效果越明显。这也让 io_uring 在处理多队列、多磁盘的存储引擎中大放异彩——攒批,本质上是在用空间换时间。
1. N_ops(总操作数):这批要打饭的人数,也就是应用想发起的全部 IO 请求。
2. B(批量大小):一叠点餐单能放多少张,对应一次提交能带走多少个 SQ 项。
3. 向上取整:即使只剩一张单子,也要跑最后一趟,不能因为不满一叠就不送。
4. 计算结果:当 B 越大,N/B 越小,系统调用次数越少,这就是“摊薄”的数学含义。
以前是“一单跑一趟”,现在成了“一叠单跑一趟”。攒批,就是 io_uring 在高 IOPS 场景下吞吐碾压式领先的秘密武器。对于存储引擎这类“一次来一大桌”的负载,这笔账算得尤其划算。
完成队列轮询
订单交出去了,怎么知道菜好了?epoll 式的后厨是“做好一道端一道”,每道菜都要喊一嗓子。io_uring 的后厨则把一桌菜全部做好,整整齐齐摆上出餐台,学生自己来看有没有自己的菜。这个“出餐台”就是完成队列(CQ)。
内核每完成一个请求,就把结果写进 CQ;应用程序从 CQ 中取出完成项,回收请求资源。取餐有两种姿势:中断模式,内核做好菜后唤醒应用来取,省电但有一次唤醒开销;轮询模式,应用自己死死盯住出餐台,每隔一小会儿就检查一次 CQ,把唤醒的等待彻底去掉。
轮询模式(IORING_SETUP_IOPOLL)适合追求极致延迟的存储场景,代价是 CPU 被持续占用。更关键的是,CQ 的消费完全在用户态进行——从 CQ 取结果不需要系统调用,这与“每次读写都要进内核”的传统 IO 有本质区别。应用把“等待”的主动权握在了自己手里。
1. T_submit(提交时间):把点餐单塞进信箱并通知后厨,对应一次 io_uring_enter 批量提交。
2. T_exec(内核执行时间):后厨真正做菜的时间,与请求类型和磁盘/网络速度相关。
3. T_poll(轮询等待时间):学生在出餐台前等待并反复查看的时间,轮询模式下由应用决定检查频率。
出餐台把“等待”的主动权交给了应用:想省 CPU 就等中断通知,想压延迟就自己盯台。这种灵活性,正是 io_uring 能同时服务“省电型”与“性能型”负载的原因——主动权在手,选择权就在手。
延迟特征对比
两种模式到底谁更快?这得先定义“快”。食堂里的等待有两种:平均排队时间,和最倒霉那位学生的等待时间(尾延迟)。前者决定体感,后者决定口碑。
epoll 是“随到随办”:事件一就绪立即处理,单请求延迟低且稳定。但逐事件串行处理,事件多了就排队,吞吐上不去。io_uring 是“攒批开席”:为了凑齐一批,早到的请求要等晚到的同伴(攒批等待),单看某个请求可能更慢了,但整批一起处理,系统调用摊薄后总吞吐大幅提升。
高 IOPS 场景下,io_uring 的吞吐优势明显;低负载、零星请求的场景,epoll 的延迟更漂亮。尤其要注意尾延迟:攒批等待就像拼车,最早到的人等最久,所以批次大小要按延迟预算谨慎调节——批次调得越大,吞吐越高,但最慢请求可能越慢。
1. IOPS(每秒完成操作数):出餐台一分钟能端出多少份菜,是吞吐指标。
2. t_done,i - t_submit,i(单个请求耗时):每个学生从下单到取餐的时长。
3. 求平均:把所有请求耗时加起来除以请求数,得到平均延迟这一体感指标。
高峰食堂里,服务员模式适合随到随办的散客,批量出餐口适合量大管饱的团体餐。没有绝对快慢,只有匹配不匹配——吞吐与延迟,从来是一枚硬币的两面。
技术选型依据
选 epoll 还是 io_uring,本质是回答一个问题:你的食堂,是什么客流特征?是一屋子人都在安静看书、偶尔有人举手?还是开饭铃一响,几百桌同时点菜?
三个关键维度:连接规模——是几千人还是几百万人;IO 密集度——每个请求是“一勺饭”还是“整桌席”;CPU 预算——愿不愿意用 CPU 换更低延迟。把它们压缩成一个指标:活跃比 R,即活跃连接数除以总连接数。R 很小,说明大部分连接在“发呆”,事件驱动的按需通知几乎零空转成本;R 很大,说明大家都很忙,批量处理能最大化利用每次系统调用。
决策规则由此而来:连接数大、活跃度低、请求轻量,选 epoll;活跃连接多、请求重量级、追求高吞吐,选 io_uring。工程现实也要算进去:io_uring 需要内核 5.1 以上,成熟特性要到 5.19+,若你的服务器内核老旧,稳定性优先选 epoll 更稳妥。
1. N_active(活跃连接数):此刻举手或正在点菜的人数,即真正有 IO 请求的连接。
2. N_total(总连接数):食堂里所有的座位数,即应用维护的全部连接。
3. 比值 R:一个介于 0 到 1 之间的数,刻画“热闹程度”,是选型的第一直觉指标。
小食堂用一位服务员绰绰有余,大食堂才需要出餐台。选型不是赶时髦,而是看客流画像——技术没有高低,只有合不合适。
实战场景分析
看三类典型“食堂”:Web 服务器是散客食堂,人来人往但随到随走;存储引擎是团体餐食堂,一开席就是几百桌同时上菜;网关是中转驿站,客流复杂,什么情况都有。
Web 服务器(Nginx、Node.js)的连接数动辄几十万,但活跃比例很低,epoll 的按需通知完美契合,CPU 占用极低——服务员站在大厅,几乎不用跑动。存储引擎(RocksDB、MySQL、Redis)则相反,随机读写密集、IOPS 需求高,io_uring 的批量提交与完成队列让吞吐显著提升——后厨一次性处理一叠订单,效率翻倍。
网关与消息队列(Kafka、Envoy)流行混合架构:用 epoll 管理海量连接的事件分发(谁来了、谁走了、谁可以写了),把真正耗时的数据读写交给 io_uring 批量处理。各取所长,是当前大型系统最务实的做法。吞吐量的账也很好算:IOPS 乘以平均请求大小,就是数据搬运的带宽。
1. IOPS(每秒操作数):每秒能处理多少个请求,代表处理频率。
2. S_req(平均请求大小):每份菜的份量,即每个请求平均读写多少字节。
3. 换算 MB/s:两者相乘得到字节每秒,再除以 1024 的平方换算成兆字节,得到直观的带宽数字。
别让服务员去端整筐菜,也别让出餐口去挨桌问需求。让合适的工具干合适的活,就是架构师的全部智慧——场景决定选型,选型决定性能上限。
性能优化方向
模式选定了,还能不能更快?答案藏在“端菜”次数里——内存拷贝。每一次数据从内核缓冲区搬到用户缓冲区,都是一次无谓的搬运。
第一个优化是固定缓冲区(registered buffers):提前把内存区域注册给内核,内核直接使用,省去每次 IO 都要做的内存固定与映射开销,就像后厨给常客预留了固定餐位。第二个是共享内存:SQ/CQ 本身就是共享内存,避免多次拷贝,信箱和食堂共用一个通道。第三个是零拷贝:配合 splice、sendfile 等机制,数据从磁盘到网卡一路不用经过用户态。
文件描述符也可以提前注册(registered files),省掉内核里反复的引用计数维护。但要注意:零拷贝适合大文件传输;小请求若频繁注册、注销,额外开销反而可能得不偿失。优化要算总账,不能只看单项收益——省下的时间占比,才是衡量优化是否值得的标尺。
1. T_orig(优化前耗时):老路线的总时间,包含所有拷贝与系统调用。
2. T_opt(优化后耗时):新路线的总时间,即应用固定缓冲区、零拷贝等手段之后的耗时。
3. 计算节省比例:两者之差除以原始耗时,得到省下的时间占比,用来横向比较不同优化手段的收益。
从“少跑腿”到“少端菜”,再到“连信箱都共享”,每一步优化都是把无意义的搬运从关键路径上删掉。食堂如此,内核 IO 亦是如此——性能优化的本质,从来都是减少浪费。
AI 应用场景
向量数据库的批量随机读加速
阶段:数据访问层性能优化
解决的问题:向量检索(如 embedding 近邻搜索)需要频繁随机读取小数据块。传统每请求一次 read 的方式,系统调用开销巨大,IOPS 上不去,检索延迟随之飙升。io_uring 的批量提交 + 完成队列轮询,把几百个随机读请求打包提交,吞吐直接上一个台阶。
具体案例:某向量检索服务在冷数据落盘后,批量召回 128 个候选向量块。改用 io_uring 并注册固定缓冲区后,单机随机读 IOPS 从 8 万提升到 15 万,P99 检索延迟下降约 40%。这相当于把“一单一跑”改成了“一叠单一次出餐”。
大模型推理网关的海量连接管理
阶段:在线推理服务接入层
解决的问题:推理网关需同时维持几十万路客户端连接,但大部分连接在等待模型输出,处于空闲状态。若为每路连接分配线程,资源瞬间耗尽。epoll 的事件驱动特性让网关只在流式输出到达时才唤醒对应协程,空闲连接近乎零成本。
具体案例:某 LLM 服务网关基于 epoll + 协程处理 60 万并发连接,流式 token 到达时按连接唤醒写入,CPU 占用稳定在 20% 以内,远低于线程池方案的 80%。这就是“聪明服务员”在 AI 时代的用武之地。
训练数据管道的零拷贝灌装
阶段:数据预处理与加载
解决的问题:大模型训练的数据加载路径“磁盘 → 内存 → 共享内存 → GPU”,传统 read + send 多次拷贝使预处理成为瓶颈。io_uring 配合注册缓冲区与零拷贝机制,让数据在页缓存与设备间直达。
具体案例:某训练平台用 io_uring + registered buffers 将图片样本从磁盘送入共享内存,省去两次用户态拷贝,数据加载吞吐提升约 40%,GPU 饥饿时间明显减少。省下的时间,都变成了 GPU 算力的有效利用。
Python 演示
class Request: def __init__(self, rid, arrive): self.rid = rid self.arrive = arrive self.done = 0.0 def run_epoll(requests, handle_time=0.05): # 模拟 epoll: 事件驱动, 逐个处理就绪请求 calls = 0 now = 0.0 result = [] for req in requests: calls += 1 now = max(now, req.arrive) + handle_time req.done = now result.append(req) return result, calls def run_iouring(requests, batch=4, exec_time=0.05): # 模拟 io_uring: 攒够一批, 一次提交, 批量完成 calls = 0 result = [] for i in range(0, len(requests), batch): group = requests[i:i + batch] calls += 2 start = max(r.arrive for r in group) for r in group: r.done = start + exec_time result.append(r) return result, calls def avg_latency(items): return sum(r.done - r.arrive for r in items) / len(items) if __name__ == "__main__": n = 16 arrivals = [i * 0.02 for i in range(n)] epoll_reqs = [Request(i, arrivals[i]) for i in range(n)] io_reqs = [Request(i, arrivals[i]) for i in range(n)] epoll_done, epoll_calls = run_epoll(epoll_reqs) io_done, io_calls = run_iouring(io_reqs, batch=4) print("epoll : syscalls =", epoll_calls, " avg_latency =", round(avg_latency(epoll_done), 3)) print("io_uring : syscalls =", io_calls, " avg_latency =", round(avg_latency(io_done), 3))
now = max(now, req.arrive) + handle_time模拟 epoll 串行处理:后到的请求必须等前面的处理完,排队时间逐渐累积。start = max(r.arrive for r in group)攒批等待:以组内最晚到达的请求为起点,体现“早到等晚到”。r.done = start + exec_time批量执行:整批同时完成,等待成本被平摊到一批请求上。return sum(r.done - r.arrive for r in items) / len(items)平均延迟 = 每个请求完成时间减到达时间的平均值,是体感指标。
常见误区
误区“io_uring 是新技术,肯定比 epoll 快,我们全部替换成 io_uring 就万事大吉了。”
正解io_uring 的优势集中在高 IOPS、批量 IO 场景。在“连接数大、活跃度低、单请求轻量”的 Web 场景,epoll 的事件驱动反而更省 CPU。io_uring 的轮询模式会持续占用 CPU 核心,攒批等待还会抬高尾延迟。选型要看客流特征,不是看技术新旧——小食堂用服务员就够了,硬上出餐台反而浪费。
为何常见:看到 benchmark 里 io_uring 吞吐数字漂亮,容易忽略它是在特定负载下测出来的;人们天然喜欢“新东西更强”的简单结论。
误区“epoll 回调里处理读写,这不就是异步吗?”
正解epoll 是同步非阻塞模型:内核只负责“通知你数据就绪了”,真正的 read/write 仍由应用自己完成,且会阻塞当前线程。真正的异步 IO 是“你发出请求,内核把数据准备好了再通知你取结果”——这正是 io_uring 做的事情。epoll 更像服务员喊“3 号桌可以打饭了”,你还得自己走去窗口;io_uring 是后厨把饭做好端上出餐台。
为何常见:“事件通知”和“异步”在概念上容易混淆,很多文章又常把 epoll 归入“IO 多路复用”而没有强调它仍是同步的。
误区“既然 io_uring 能轮询完成队列,那延迟应该比 epoll 更低,延迟敏感服务都该换。”
正解轮询模式只是去掉了中断唤醒的开销,但引入了持续的 CPU 占用;并且攒批机制会引入等待延迟。在低负载下,epoll 的中断通知反而延迟更低。延迟敏感要看 p99 甚至 p999,io_uring 若批次配置不当,攒批等待会成为新的尾部延迟来源——拼车虽然人均便宜,但最早到的人等最久。
为何常见:把“无中断开销”等同于“无延迟”,忽略了轮询本身的成本与攒批等待的代价。
误区“io_uring 不是号称零拷贝吗?我只要用了它,数据拷贝就没了。”
正解io_uring 本身解决的是“系统调用次数”问题,零拷贝需要额外的特性组合:固定缓冲区(registered buffers)、注册文件描述符、配合 splice/sendmsg 等机制。默认配置下,数据仍然可能经过内核缓冲区到用户缓冲区的拷贝。它只是提供了“做到零拷贝”的通道,不是默认开启——就像食堂提供了出餐台,但菜还是要人端。
为何常见:营销文章常把“io_uring + 零拷贝”当作整体宣传,读者容易把配套机制误认为内置默认行为。
面试题
Q1. epoll 相比传统 select/poll 的逐连接轮询,最核心的改进是?
- A.一次处理全部就绪连接
- B.只通知就绪的事件← 正确答案
- C.使用共享内存传递数据
- D.内核主动推送完整数据
答案:B。select/poll 每次都要遍历全部连接,复杂度 O(n);epoll 只返回就绪事件,复杂度降为 O(就绪数)。就像服务员只会告诉你谁举手了,而不是让你挨桌问一遍。C、D 都不是 epoll 的机制。
Q2. 关于 io_uring 的提交队列(SQ),下列说法正确的是?
- A.应用直接写内核环形队列← 正确答案
- B.每次提交前必须锁定内存
- C.队列只能存放读请求
- D.内核从网络拉取请求
答案:A。SQ 是用户态与内核共享的环形缓冲区,应用把请求描述直接写入,再统一提交。它既可放读也可放写请求,C 错;锁定内存是“固定缓冲区”的可选优化而非必须,B 错;内核不会主动拉取,D 错。
Q3. “连接数很大但活跃连接很少”的场景选择 epoll 更合理,主要原因是?
- A.epoll 支持零拷贝特性
- B.事件驱动按需唤醒开销低← 正确答案
- C.epoll 无需分配任何队列
- D.就绪事件唤醒耗时恒定
答案:B。事件驱动只在就绪时唤醒应用,空闲连接不产生任何成本,这是 epoll 省 CPU 的根本原因。零拷贝不是 epoll 的特性,A 错;epoll 内部也要维护监听集合,C 错;“恒定”表述绝对化,D 错。
Q4. 与传统阻塞 IO 相比,epoll 带来的优势包括?
- A.支持海量并发连接← 正确答案
- B.降低就绪事件查找开销← 正确答案
- C.自动并行数据拷贝
- D.避免空轮询浪费 CPU← 正确答案
答案:ABD。epoll 通过注册加事件通知支持海量连接,查找复杂度为 O(就绪数),且不必轮询无事件的连接,A、B、D 正确。数据拷贝仍需应用代码完成,内核不会自动并行拷贝,C 错。
Q5. 应用长期不消费完成队列(CQ),最可能发生的后果是?
- A.内核会丢弃早期完成事件
- B.请求资源将无法被回收← 正确答案
- C.完成队列会自动扩容
- D.内核主动暂停全部请求
答案:B。CQ 里的完成项对应底层请求资源,不消费就无法释放,长期会泄漏;CQ 写满后新的完成事件无处存放,新提交也会失败或阻塞。内核不会静默丢弃(A)或擅自扩容(C),更不会主动暂停全部请求(D)。
Q6. io_uring 降低系统调用开销的核心机制是?
- A.批量提交多个 IO 请求← 正确答案
- B.缩短单次系统调用耗时
- C.将请求移交独立线程池
- D.减少内核中断的次数
答案:A。io_uring 的核心是把 N 次操作合并成 N/B 次系统调用,这是批量提交的本质。它并不缩短单次调用耗时(B),也不依赖线程池(C),中断次数的减少只是批量化的副产品(D)。
Q7. 关于水平触发(LT)与边缘触发(ET),下列说法正确的是?
- A.LT 会反复提醒未读数据← 正确答案
- B.ET 只在状态变化时提醒← 正确答案
- C.使用 ET 能减少通知次数← 正确答案
- D.使用 ET 必须多线程处理
答案:ABC。LT 只要缓冲区还有数据就持续通知,ET 只在状态变化瞬间通知一次,因此 ET 能减少重复通知。ET 并不强制要求多线程,只要求应用一次性把数据读完,D 表述过度绝对,错误。
Q8. 存储引擎这类高 IOPS 场景采用 io_uring 更合适,主要原因是?
- A.支持单连接高并发处理
- B.批量提交摊薄内核开销← 正确答案
- C.自动均衡磁盘读写负载
- D.缩短单次中断响应延时
答案:B。高 IOPS 意味着高频操作,批量提交把每次操作的系统调用成本摊薄到一批上,收益随频率放大。单连接高并发处理不是 io_uring 特有的,A 错;它不负责磁盘负载均衡,C 错;它不能缩短单次中断响应,D 错。
Q9. 要让 io_uring 进一步降低每次 IO 的开销,可以搭配的机制有?
- A.注册固定缓冲区← 正确答案
- B.映射共享内存区← 正确答案
- C.提前注册文件描述符← 正确答案
- D.增大提交队列深度
答案:ABC。固定缓冲区省去反复的内存固定与映射开销,共享内存映射避免拷贝,注册文件描述符省去引用计数维护,A、B、C 正确。增大提交队列深度只影响批大小,不降低单次 IO 开销,D 错。
Q10. 高并发网关希望兼顾海量连接管理与高吞吐数据 IO,最合理的架构是?
- A.只使用 io_uring 技术
- B.epoll 与 io_uring 混用← 正确答案
- C.关闭 CQ 改用信号通知
- D.连接全改为阻塞模式
答案:B。成熟的混合架构用 epoll 管理海量连接的事件分发,把重 IO 交给 io_uring 批量处理,各取所长。只用 io_uring 会在纯连接管理上付出不必要的轮询代价(A);CQ 是完成通知的核心,不能关闭(C);阻塞模式无法支撑高并发(D)。