epoll EPOLLONESHOT 怎样正确重新武装事件
使用 EPOLLONESHOT 后,描述符的一次事件被 epoll_wait 返回,相关通知就会被禁用。处理完成且连接仍需继续监听时,调用 epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event),重新提交关注掩码与用户数据,才能恢复通知。
不要把“重新进入 epoll_wait”当成重武装。本文先给最小写法,再用事件数量和读取字节数说明差异;原创示例没有在本机执行,也不提供虚构的性能提升或延迟数据。
Linux man-pages 官方项目资料:https://man7.org/linux/man-pages/man7/epoll.7.html。本地连接对接口:https://man7.org/linux/man-pages/man2/socketpair.2.html。
先看计数:首次通知后是否还有后续事件
典型现象是首次收到可读事件后,连接后续仍有输入,却再也没有处理机会。诊断时把 epoll_wait 返回事件数、实际读取字节数和 MOD 调用结果分开记录。仅看“对端成功写入”,不能证明当前监听已经重新启用。
本例只关注一个本地连接、两个单字节写入和一次重武装。计数用于看清通知语义,不用于推断真实服务器能承载多少连接。正式负载还涉及调度、业务处理与缓冲管理,不能由这个短程序得出吞吐结论。
行为假设:ONESHOT 暂停的是通知
ONESHOT 并不替你读走数据,也不代表关闭 fd。它限制的是此项监听后续的通知。应用仍然拥有连接状态,必须决定这次处理后继续监听、等待写就绪,还是关闭连接。
把处理读到暂时无数据和恢复监听作为明确的交接点,有助于避免遗漏后续通知。多线程场景中,这个交接还必须配合连接所有权;ONESHOT 不能代替应用对对象生命期的管理。

改动点:完整掩码配合 EPOLL_CTL_MOD
/* 工作线程完成当前连接处理后,更新完整的关注掩码。 */
struct epoll_event interest = {0};
interest.events = EPOLLIN | EPOLLONESHOT;
interest.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &interest) == -1) {
/* 不忽略失败:由调用方记录并处理连接生命周期。 */
perror("epoll_ctl MOD");
}
这个片段假设 epfd 和 fd 已经有效、fd 已在该 epoll 实例中注册。MOD 是更新已有注册项,不是再次 ADD。每次都构造本轮需要的完整掩码,并保留 EPOLLONESHOT;若原设计还采用 EPOLLET 等标志,也要按原有模式明确提交。
data.fd 是本例选择的事件识别字段。若实际项目使用 data.ptr 指向连接对象,应继续提交正确且有效的对象指针,不能照抄 fd 字段后丢掉应用的分发信息。失败时要记录并进入明确的恢复或关闭路径,不能假设重武装总会成功。
复现方法:用 socketpair 隔离外部网络
下面的完整程序使用非阻塞 AF_UNIX socketpair,避开远端服务和监听端口。第一轮读到 EAGAIN 后写入第二个字节,先做一次零超时等待观察未武装状态,再 MOD 并观察已有可读数据。本例采用水平触发加 ONESHOT,刻意不混入 EPOLLET。
#include#include #include #include #include static int wait_event(int epfd, struct epoll_event *event, int timeout) { int count; do { count = epoll_wait(epfd, event, 1, timeout); } while (count == -1 && errno == EINTR); /* 中断后重新等待。 */ return count; } static int drain_read(int fd, int *bytes) { char buffer[64]; for (;;) { ssize_t n = read(fd, buffer, sizeof buffer); if (n > 0) { *bytes += (int)n; continue; } if (n == 0) return 0; /* 对端关闭,不再继续重武装读取。 */ if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) return 1; return -1; } } int main(void) { int pair[2] = {-1, -1}, epfd = -1, result = 1, bytes = 0; struct epoll_event interest = {0}, event = {0}; /* 本地连接对均为非阻塞,不依赖监听端口或远程服务。 */ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, pair) == -1) goto error; epfd = epoll_create1(EPOLL_CLOEXEC); if (epfd == -1) goto error; interest.events = EPOLLIN | EPOLLONESHOT; /* 本例不使用 EPOLLET。 */ interest.data.fd = pair[0]; if (epoll_ctl(epfd, EPOLL_CTL_ADD, pair[0], &interest) == -1) goto error; if (write(pair[1], "A", 1) != 1) goto error; int first = wait_event(epfd, &event, 1000); if (first == -1) goto error; if (first != 1 || !(event.events & EPOLLIN)) goto unexpected; if (drain_read(pair[0], &bytes) != 1) goto unexpected; /* 写入第二个字节时,ONESHOT 的通知尚未恢复。 */ if (write(pair[1], "B", 1) != 1) goto error; int disabled = wait_event(epfd, &event, 0); if (disabled == -1) goto error; /* MOD 重新提交掩码和 data,恢复后续就绪通知。 */ if (epoll_ctl(epfd, EPOLL_CTL_MOD, pair[0], &interest) == -1) goto error; int rearmed = wait_event(epfd, &event, 1000); if (rearmed == -1) goto error; if (rearmed != 1 || !(event.events & EPOLLIN)) goto unexpected; if (drain_read(pair[0], &bytes) != 1) goto unexpected; printf("first=%d disabled=%d rearmed=%d bytes=%d\n", first, disabled, rearmed, bytes); result = 0; goto cleanup; error: perror("演示系统调用失败"); goto cleanup; unexpected: fprintf(stderr, "未观察到本例要求的可读状态,请检查运行环境\n"); cleanup: /* 所有出口都释放本例拥有的描述符。 */ if (epfd != -1) close(epfd); if (pair[0] != -1) close(pair[0]); if (pair[1] != -1) close(pair[1]); return result; }
保存为 oneshot_demo.c,在 Linux 环境中可使用以下命令自行运行。本文未执行这些命令,图片也不作为运行证据。
# 编译原创示例,打开常用警告。 cc -std=c11 -Wall -Wextra -O2 oneshot_demo.c -o oneshot_demo # 运行后查看事件数与读取总字节数。 ./oneshot_demo

结果对比:缺少重武装与恢复通知
| 观察项 | 本例正常条件下的语义预期 | 说明 |
|---|---|---|
| first | 1 | 首次就绪被返回 |
| disabled | 0 | 未重武装时没有后续通知 |
| rearmed | 1 | MOD 后再次取得可读通知 |
| bytes | 2 | 两个单字节写入均由读循环消费 |
这些数字是示例输入与 API 语义推导,不是实测结果。若删掉 MOD,不能继续期待第二次等待必然获得输入通知。若连接在实际业务中已关闭,则不应机械重武装;先判断连接是否继续存在和需要哪些方向的事件。
读取循环处理 EINTR、EAGAIN 与 EOF,避免把暂时无数据当成断连。此处的缓冲只做计数,没有实现应用协议;真实程序还要把字节组装成消息,而不是认为一次可读通知对应一条完整消息。
边界条件和常见问题
必须把 EPOLLONESHOT 和 EPOLLET 一起使用吗?
不是同一个开关。本文没有使用 EPOLLET。若采用边沿触发,非阻塞读写与处理到 EAGAIN 尤其重要,不能只增加 ONESHOT 就忽略原有读写约定。
为什么不先重武装再让工作线程处理?
如果当前线程仍在修改连接缓冲和状态,过早恢复可能让别的等待线程取得后续事件。应先定义谁拥有连接和何时交还监听,再安排 MOD,而不是把它当作随处可调用的解锁操作。
EPOLLONESHOT 是否保证一个 fd 永远只由一个线程接触?
不能。其他应用代码仍可能直接读写、关闭或重复提交这个连接。对象释放、fd 复用和陈旧事件都需要自己的同步与身份管理。
每次都监听 EPOLLOUT 会更稳吗?
不应为重武装无条件添加写关注。根据实际待发送缓冲决定是否需要写就绪,避免没有业务工作却频繁处理可写状态。重武装的目标是恢复正确的关注集合。
Linux cgroup v2 memory.stat 怎样区分匿名页和文件页
- 上一篇
- Linux cgroup v2 memory.stat 怎样区分匿名页和文件页
- 下一篇
- Linux nsenter 怎么同时进入目标进程的 network 和 mount namespace
-
- 文章 · linux | 1小时前 |
- coredumpctl 怎样按 PID 导出转储
- 478浏览 收藏
-
- 文章 · linux | 1小时前 |
- systemd.service notify 怎样处理 READY=1
- 451浏览 收藏
-
- 文章 · linux | 1小时前 |
- systemd.exec 怎样限制文件系统访问
- 294浏览 收藏
-
- 文章 · linux | 1小时前 |
- Linux nsenter 怎么同时进入目标进程的 network 和 mount namespace
- 263浏览 收藏
-
- 文章 · linux | 1小时前 |
- Linux cgroup v2 memory.stat 怎样区分匿名页和文件页
- 405浏览 收藏
-
- 文章 · linux | 1小时前 |
- io_uring SQPOLL 怎样理解轮询行为
- 200浏览 收藏
-
- 文章 · linux | 1小时前 |
- journalctl cursor 怎样断点读取日志
- 203浏览 收藏
-
- 文章 · linux | 1小时前 |
- resource-control 怎样限制服务内存
- 425浏览 收藏
-
- 文章 · linux | 1小时前 | linux运维 · 故障排查 · Linux systemd 安全加固 systemd-analyze 服务沙箱
- systemd-analyze security 怎样查看沙箱暴露面
- 487浏览 收藏
-
- 文章 · linux | 1小时前 |
- systemd-path 怎么查询当前用户的配置目录
- 442浏览 收藏
-
- 文章 · linux | 1小时前 |
- systemd.socket 怎样继承监听套接字
- 128浏览 收藏
-
- 文章 · linux | 1小时前 |
- systemd.path 怎样按文件变化触发服务
- 342浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 686次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 701次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 757次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 719次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 557次使用
-
- Linux vmstat 如何分辨内存抖动与磁盘等待:si、so、wa 与复测顺序
- 2026-08-30 501浏览
-
- Linux搭建vsftpdFTP服务器教程
- 2026-04-30 501浏览
-
- Shell脚本安装教程:.sh一键安装指南
- 2026-03-16 501浏览
-
- Linux清空文件内容的几种方法
- 2025-12-01 501浏览
-
- Linux命令行下载文件技巧
- 2025-11-23 501浏览

