<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>IO多路复用 on 吾爱主机</title><link>https://blog.waihost.com/tags/io%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/</link><description>Recent content in IO多路复用 on 吾爱主机</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Tue, 28 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.waihost.com/tags/io%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/index.xml" rel="self" type="application/rss+xml"/><item><title>Linux epoll I/O 多路复用：水平触发、边缘触发与工程实践</title><link>https://blog.waihost.com/posts/linux-epoll-io-multiplexing-lt-et-practice/</link><pubDate>Tue, 28 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/linux-epoll-io-multiplexing-lt-et-practice/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/linux-epoll-io-multiplexing-lt-et-practice.svg" alt="Featured image of post Linux epoll I/O 多路复用：水平触发、边缘触发与工程实践" /&gt;&lt;p&gt;高并发服务里最常见的一类卡顿不是“CPU 不够”，而是 &lt;strong&gt;大量连接上只有少量真正可读/可写&lt;/strong&gt;。若对每个连接开线程阻塞 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt;，上下文切换与内存开销会迅速失控；若用轮询扫所有 fd，又会把 CPU 烧掉。Linux 提供的 &lt;strong&gt;epoll&lt;/strong&gt; 就是为“只在就绪时被唤醒”设计的 I/O 事件通知机制。&lt;/p&gt;
&lt;p&gt;本文基于 Linux man-pages 的 &lt;code&gt;epoll(7)&lt;/code&gt;、&lt;code&gt;epoll_create(2)&lt;/code&gt;/&lt;code&gt;epoll_create1(2)&lt;/code&gt;、&lt;code&gt;epoll_ctl(2)&lt;/code&gt;、&lt;code&gt;epoll_wait(2)&lt;/code&gt;，并结合 Python &lt;code&gt;selectors&lt;/code&gt; 与 Nginx 连接处理文档，把 &lt;strong&gt;interest/ready 两张表、LT/ET 语义、ONESHOT、非阻塞与 EAGAIN、常见坑与排障&lt;/strong&gt; 讲清楚，方便你在自研事件循环或排查 Nginx/Redis 类服务时按机制定位。&lt;/p&gt;
&lt;h2 id="一问题背景为什么-selectpoll-会长不大"&gt;&lt;a href="#%e4%b8%80%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e4%b8%ba%e4%bb%80%e4%b9%88-selectpoll-%e4%bc%9a%e9%95%bf%e4%b8%8d%e5%a4%a7" class="header-anchor"&gt;&lt;/a&gt;一、问题背景：为什么 select/poll 会“长不大”
&lt;/h2&gt;&lt;p&gt;传统 &lt;code&gt;select(2)&lt;/code&gt; / &lt;code&gt;poll(2)&lt;/code&gt; 也能多路复用，但工程上有两个长期痛点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每次调用都要反复提交关注集合&lt;/strong&gt;。调用者把“我关心哪些 fd、关心读还是写”整包传给内核；内核再线性扫描。连接数上来后，用户态↔内核态的拷贝与扫描成本随 N 增长。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;就绪集合表达方式不友好&lt;/strong&gt;。&lt;code&gt;select&lt;/code&gt; 还有历史性的 fd 数量上限（与 &lt;code&gt;FD_SETSIZE&lt;/code&gt; 相关），大规模长连接场景很难优雅扩展。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;epoll 的定位不同：它在内核里维护一个 &lt;strong&gt;epoll 实例&lt;/strong&gt;（本质是“容器 + 回调”），应用 &lt;strong&gt;只注册一次兴趣&lt;/strong&gt;，之后用 &lt;code&gt;epoll_wait&lt;/code&gt; &lt;strong&gt;拉取当前就绪事件&lt;/strong&gt;。man 页明确指出：epoll 既可作 &lt;strong&gt;水平触发（level-triggered, LT）&lt;/strong&gt; 也可作 &lt;strong&gt;边缘触发（edge-triggered, ET）&lt;/strong&gt;，并且 &lt;strong&gt;能较好扩展到大量被监视的文件描述符&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="二核心机制interest-list-与-ready-list"&gt;&lt;a href="#%e4%ba%8c%e6%a0%b8%e5%bf%83%e6%9c%ba%e5%88%b6interest-list-%e4%b8%8e-ready-list" class="header-anchor"&gt;&lt;/a&gt;二、核心机制：interest list 与 ready list
&lt;/h2&gt;&lt;p&gt;从用户视角看，一个 epoll 实例可理解为两张表（&lt;code&gt;epoll(7)&lt;/code&gt; 原文概念）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;结构&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;interest list&lt;/strong&gt;（epoll set）&lt;/td&gt;
					&lt;td&gt;进程通过 &lt;code&gt;epoll_ctl&lt;/code&gt; 注册、希望监视的 fd 集合&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ready list&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;interest list 的子集（更准确：其中“就绪”的引用集合），由内核因 I/O 活动动态填充&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;配套系统调用职责清晰：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;调用&lt;/th&gt;
					&lt;th&gt;作用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_create&lt;/code&gt; / &lt;code&gt;epoll_create1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;创建 epoll 实例，返回 epoll 自身的 fd&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_ctl&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;对 interest list 做 &lt;code&gt;ADD&lt;/code&gt; / &lt;code&gt;MOD&lt;/code&gt; / &lt;code&gt;DEL&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_wait&lt;/code&gt; / &lt;code&gt;epoll_pwait&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;从 ready list 取回最多 &lt;code&gt;maxevents&lt;/code&gt; 个事件；无事件时可阻塞&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="21-创建实例优先-epoll_create1"&gt;&lt;a href="#21-%e5%88%9b%e5%bb%ba%e5%ae%9e%e4%be%8b%e4%bc%98%e5%85%88-epoll_create1" class="header-anchor"&gt;&lt;/a&gt;2.1 创建实例：优先 &lt;code&gt;epoll_create1&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;epoll_create(size)&lt;/code&gt; 在 Linux 2.6.8 之后 &lt;strong&gt;忽略 size&lt;/strong&gt;（但仍要求 &lt;code&gt;size &amp;gt; 0&lt;/code&gt;）。更推荐 &lt;code&gt;epoll_create1(flags)&lt;/code&gt;：可传 &lt;code&gt;EPOLL_CLOEXEC&lt;/code&gt;，避免 &lt;code&gt;fork&lt;/code&gt;+&lt;code&gt;exec&lt;/code&gt; 时泄漏 epoll fd（与 &lt;code&gt;open(2)&lt;/code&gt; 的 &lt;code&gt;O_CLOEXEC&lt;/code&gt; 同理）。&lt;/p&gt;
&lt;h3 id="22-注册兴趣epoll_ctl-与事件掩码"&gt;&lt;a href="#22-%e6%b3%a8%e5%86%8c%e5%85%b4%e8%b6%a3epoll_ctl-%e4%b8%8e%e4%ba%8b%e4%bb%b6%e6%8e%a9%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;2.2 注册兴趣：&lt;code&gt;epoll_ctl&lt;/code&gt; 与事件掩码
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* EPOLLIN | EPOLLOUT | ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;epoll_data_t&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* 用户数据：常存 fd 或指针 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;常用 &lt;code&gt;op&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EPOLL_CTL_ADD&lt;/code&gt;：加入 interest list&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EPOLL_CTL_MOD&lt;/code&gt;：修改已有项的事件掩码/用户数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EPOLL_CTL_DEL&lt;/code&gt;：从 interest list 删除&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见事件位（&lt;code&gt;epoll_ctl(2)&lt;/code&gt;）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;标志&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLIN&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;可读（含对端数据、监听 socket 有新连接等）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLOUT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;可写&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLRDHUP&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;流式套接字对端关闭或关闭写半部（2.6.17+，ET 下检测对端关闭很有用）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLPRI&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;带外/异常条件（语义见 &lt;code&gt;poll(2)&lt;/code&gt; 的 &lt;code&gt;POLLPRI&lt;/code&gt;）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLERR&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;错误；&lt;strong&gt;无需&lt;/strong&gt;在 &lt;code&gt;events&lt;/code&gt; 里显式设置，&lt;code&gt;epoll_wait&lt;/code&gt; 总会报告&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLHUP&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;hang up；同样 &lt;strong&gt;总会报告&lt;/strong&gt;，不必手动设置&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLET&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;请求 &lt;strong&gt;边缘触发&lt;/strong&gt;（默认是水平触发）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLONESHOT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;通知一次后自动禁用该 fd，需 &lt;code&gt;MOD&lt;/code&gt; 重新武装&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLEXCLUSIVE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;多 epoll 监听同一 fd 时的互斥唤醒（4.5+）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;工程建议：&lt;strong&gt;监听 socket 与连接 socket 都建议设为非阻塞&lt;/strong&gt;；读写方向若会频繁切换，可一次 &lt;code&gt;ADD&lt;/code&gt; 时就挂 &lt;code&gt;EPOLLIN|EPOLLOUT&lt;/code&gt;，避免来回 &lt;code&gt;MOD&lt;/code&gt; 切换。&lt;/p&gt;
&lt;h3 id="23-等待就绪epoll_wait"&gt;&lt;a href="#23-%e7%ad%89%e5%be%85%e5%b0%b1%e7%bb%aaepoll_wait" class="header-anchor"&gt;&lt;/a&gt;2.3 等待就绪：&lt;code&gt;epoll_wait&lt;/code&gt;
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxevents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;maxevents&lt;/code&gt; 必须 &lt;strong&gt;&amp;gt; 0&lt;/strong&gt;，表示输出数组容量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeout&lt;/code&gt; 单位毫秒：&lt;code&gt;0&lt;/code&gt; 立即返回；&lt;code&gt;-1&lt;/code&gt; 一直阻塞；正数限时等待。时间基准为 &lt;code&gt;CLOCK_MONOTONIC&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;返回值：就绪事件个数；&lt;code&gt;0&lt;/code&gt; 表示超时；&lt;code&gt;-1&lt;/code&gt; 出错（常见 &lt;code&gt;EINTR&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;返回的 &lt;code&gt;events[i].events&lt;/code&gt; 是 &lt;strong&gt;实际发生&lt;/strong&gt; 的事件掩码；&lt;code&gt;data&lt;/code&gt; 原样带回你在 &lt;code&gt;ctl&lt;/code&gt; 时写入的用户数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;epoll_wait&lt;/code&gt; 的返回策略还会尽量避免 &lt;strong&gt;饥饿&lt;/strong&gt;：若某次只处理“已经知道就绪”的一小撮 fd，而忽略其他已就绪 fd，系统可能长期“看不见”后者。应用层也应做 &lt;strong&gt;就绪队列 + 轮转处理&lt;/strong&gt;，而不是在单次事件上无限 &lt;code&gt;read&lt;/code&gt; 到饿死其它连接（man 页 “Starvation” 小节专门提醒）。&lt;/p&gt;
&lt;h2 id="三水平触发-vs-边缘触发语义决定写法"&gt;&lt;a href="#%e4%b8%89%e6%b0%b4%e5%b9%b3%e8%a7%a6%e5%8f%91-vs-%e8%be%b9%e7%bc%98%e8%a7%a6%e5%8f%91%e8%af%ad%e4%b9%89%e5%86%b3%e5%ae%9a%e5%86%99%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;三、水平触发 vs 边缘触发：语义决定写法
&lt;/h2&gt;&lt;p&gt;这是 epoll 最容易写错的地方。&lt;code&gt;epoll(7)&lt;/code&gt; 用管道例子讲得很直白：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把管道读端 &lt;code&gt;rfd&lt;/code&gt; 加入 epoll；&lt;/li&gt;
&lt;li&gt;写端写入 2KB；&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;epoll_wait&lt;/code&gt; → 报告 &lt;code&gt;rfd&lt;/code&gt; 可读；&lt;/li&gt;
&lt;li&gt;应用只读了 1KB，缓冲区仍剩数据；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;再次&lt;/strong&gt; &lt;code&gt;epoll_wait&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LT（默认）&lt;/strong&gt;：只要缓冲区仍“可读”，就会 &lt;strong&gt;继续&lt;/strong&gt; 报告就绪；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ET（&lt;code&gt;EPOLLET&lt;/code&gt;）&lt;/strong&gt;：只在状态 &lt;strong&gt;发生变化&lt;/strong&gt; 时报告；若你没把数据读到“暂时不可读”，可能 &lt;strong&gt;不再通知&lt;/strong&gt;，下一次 &lt;code&gt;wait&lt;/code&gt; 会“卡住”，尽管缓冲里还有数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="31-lt像-poll好写易吵"&gt;&lt;a href="#31-lt%e5%83%8f-poll%e5%a5%bd%e5%86%99%e6%98%93%e5%90%b5" class="header-anchor"&gt;&lt;/a&gt;3.1 LT：像 poll，好写，易“吵”
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;语义接近 &lt;code&gt;poll&lt;/code&gt;：条件成立就通知。&lt;/li&gt;
&lt;li&gt;适合：业务逻辑简单、可接受重复通知、希望少踩坑。&lt;/li&gt;
&lt;li&gt;风险：若某 fd 长期可读但你处理很慢，它会 &lt;strong&gt;反复&lt;/strong&gt; 出现在 ready list，拖慢整个循环。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="32-et变化才通知必须抽干--非阻塞"&gt;&lt;a href="#32-et%e5%8f%98%e5%8c%96%e6%89%8d%e9%80%9a%e7%9f%a5%e5%bf%85%e9%a1%bb%e6%8a%bd%e5%b9%b2--%e9%9d%9e%e9%98%bb%e5%a1%9e" class="header-anchor"&gt;&lt;/a&gt;3.2 ET：变化才通知，必须“抽干 + 非阻塞”
&lt;/h3&gt;&lt;p&gt;man 页对 ET 的建议可压缩成两条硬规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;fd 必须非阻塞&lt;/strong&gt;。否则一次阻塞 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt; 可能饿死处理其它 fd 的任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只有在 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt; 返回 &lt;code&gt;EAGAIN&lt;/code&gt;（或等价“暂时不可用”）之后，才回去 &lt;code&gt;epoll_wait&lt;/code&gt;&lt;/strong&gt;。也就是说，事件到来后要 &lt;strong&gt;循环读写直到资源暂时耗尽&lt;/strong&gt;，不能“读一次就回 wait”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ET 适合：超高连接数、希望减少重复唤醒、团队能严格遵守非阻塞与 drain 协议的场景。很多高性能网络库默认 ET + 非阻塞。&lt;/p&gt;
&lt;h3 id="33-epolloneshot一次性通知与重武装"&gt;&lt;a href="#33-epolloneshot%e4%b8%80%e6%ac%a1%e6%80%a7%e9%80%9a%e7%9f%a5%e4%b8%8e%e9%87%8d%e6%ad%a6%e8%a3%85" class="header-anchor"&gt;&lt;/a&gt;3.3 &lt;code&gt;EPOLLONESHOT&lt;/code&gt;：一次性通知与重武装
&lt;/h3&gt;&lt;p&gt;设置 &lt;code&gt;EPOLLONESHOT&lt;/code&gt; 后，某个 fd 被 &lt;code&gt;epoll_wait&lt;/code&gt; 报告一次，就会在 interest list 中被 &lt;strong&gt;禁用&lt;/strong&gt;，直到你用 &lt;code&gt;EPOLL_CTL_MOD&lt;/code&gt; 重新启用。它常用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多线程 worker：保证同一连接同一时刻只被一个线程处理；&lt;/li&gt;
&lt;li&gt;防止在处理完成前被其它线程再次取到同一事件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代价是：处理结束后 &lt;strong&gt;必须记得 rearm&lt;/strong&gt;，否则该连接“静音”。&lt;/p&gt;
&lt;h2 id="四最小可运行实验管道上的-lt-vs-et"&gt;&lt;a href="#%e5%9b%9b%e6%9c%80%e5%b0%8f%e5%8f%af%e8%bf%90%e8%a1%8c%e5%ae%9e%e9%aa%8c%e7%ae%a1%e9%81%93%e4%b8%8a%e7%9a%84-lt-vs-et" class="header-anchor"&gt;&lt;/a&gt;四、最小可运行实验：管道上的 LT vs ET
&lt;/h2&gt;&lt;p&gt;下面用 Python 演示 &lt;strong&gt;同一场景在 LT/ET 下的不同表现&lt;/strong&gt;（Linux 上 &lt;code&gt;selectors.EpollSelector&lt;/code&gt;；其它平台会落到 kqueue/select 等，行为不完全等价）。目标不是抄生产框架，而是把 man 页语义变成可观测实验。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;LT vs ET demo on a pipe (Linux).&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;select&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_pipe&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# set non-blocking on read end for ET-safe drain&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_blocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_blocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_pipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epoll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPOLLIN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPOLLET&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mode:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ET&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;LT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# first wait should fire&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; first poll:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# only read 1024, leave 1024 in buffer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; partial read:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# second wait: LT should still report; ET often does not&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ev2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; second poll:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# drain remaining&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;BlockingIOError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; drained rest:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;---&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;预期现象（内核版本与缓冲细节可能略有差异，但方向稳定）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LT&lt;/strong&gt;：partial read 后第二次 &lt;code&gt;poll&lt;/code&gt; &lt;strong&gt;仍可能&lt;/strong&gt;看到 &lt;code&gt;EPOLLIN&lt;/code&gt;（缓冲仍可读）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ET&lt;/strong&gt;：partial read 后第二次 &lt;code&gt;poll&lt;/code&gt; 往往 &lt;strong&gt;超时为空&lt;/strong&gt;，直到再次有“新数据写入”或你换回 LT/ONESHOT 策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这正是生产代码里 “ET 下偶发假死：连接其实有数据，事件循环却不再被唤醒” 的最小复现。&lt;/p&gt;
&lt;h3 id="41-监听-socket-的标准骨架伪代码"&gt;&lt;a href="#41-%e7%9b%91%e5%90%ac-socket-%e7%9a%84%e6%a0%87%e5%87%86%e9%aa%a8%e6%9e%b6%e4%bc%aa%e4%bb%a3%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;4.1 监听 socket 的标准骨架（伪代码）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/* listener, conn 均为 nonblocking */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EPOLLIN&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;use_et&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;EPOLLET&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_EVENTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ET 下要循环 accept 到 EAGAIN */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errno&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;EAGAIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* handle error */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;set_nonblocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EPOLLIN&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;EPOLLRDHUP&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;use_et&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;EPOLLET&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;do_use_fd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cm"&gt;/* ET: 读写到 EAGAIN；注意 EPOLLERR/HUP */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;do_use_fd&lt;/code&gt; 在 ET 下必须把 &lt;strong&gt;可读数据读尽、可写缓冲区写满到 EAGAIN&lt;/strong&gt;；半包协议要在用户态自己做缓冲拼装。&lt;/p&gt;
&lt;h2 id="五工程映射语言运行时与-nginx"&gt;&lt;a href="#%e4%ba%94%e5%b7%a5%e7%a8%8b%e6%98%a0%e5%b0%84%e8%af%ad%e8%a8%80%e8%bf%90%e8%a1%8c%e6%97%b6%e4%b8%8e-nginx" class="header-anchor"&gt;&lt;/a&gt;五、工程映射：语言运行时与 Nginx
&lt;/h2&gt;&lt;h3 id="51-python-selectors"&gt;&lt;a href="#51-python-selectors" class="header-anchor"&gt;&lt;/a&gt;5.1 Python &lt;code&gt;selectors&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;标准库 &lt;code&gt;selectors&lt;/code&gt; 把 epoll/kqueue/select 统一成高层 API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DefaultSelector&lt;/code&gt;：当前平台最高效实现的别名（Linux 上通常是 &lt;code&gt;EpollSelector&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EVENT_READ&lt;/code&gt; / &lt;code&gt;EVENT_WRITE&lt;/code&gt;：跨平台事件抽象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;register&lt;/code&gt; / &lt;code&gt;unregister&lt;/code&gt; / &lt;code&gt;modify&lt;/code&gt; / &lt;code&gt;select&lt;/code&gt;：对应 ctl + wait 的职责拆分。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自研 asyncio 之外的小工具时，优先用 &lt;code&gt;selectors&lt;/code&gt;，比直接 &lt;code&gt;select.epoll&lt;/code&gt; 更易移植；但若你要精细控制 &lt;code&gt;EPOLLET&lt;/code&gt;/&lt;code&gt;ONESHOT&lt;/code&gt;，仍需回到平台原生接口。&lt;/p&gt;
&lt;h3 id="52-nginx-的连接处理方式"&gt;&lt;a href="#52-nginx-%e7%9a%84%e8%bf%9e%e6%8e%a5%e5%a4%84%e7%90%86%e6%96%b9%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;5.2 Nginx 的连接处理方式
&lt;/h3&gt;&lt;p&gt;Nginx 文档 &lt;em&gt;Connection processing methods&lt;/em&gt; 写明：Linux 2.6+ 使用高效的 &lt;strong&gt;epoll&lt;/strong&gt; 方法，并支持 &lt;code&gt;EPOLLRDHUP&lt;/code&gt;、&lt;code&gt;EPOLLEXCLUSIVE&lt;/code&gt; 等标志。平台支持多种方法时，Nginx 会自动选最高效者，也可用 &lt;code&gt;use&lt;/code&gt; 指令显式指定。这解释了为何“同一套配置在 Linux 与 FreeBSD 上行为细节不同”——底层分别是 epoll 与 kqueue。&lt;/p&gt;
&lt;h2 id="六常见坑与排查清单"&gt;&lt;a href="#%e5%85%ad%e5%b8%b8%e8%a7%81%e5%9d%91%e4%b8%8e%e6%8e%92%e6%9f%a5%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;六、常见坑与排查清单
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;症状&lt;/th&gt;
					&lt;th&gt;更可能的机制原因&lt;/th&gt;
					&lt;th&gt;排查/修复&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;ET 下连接“有数据但不再触发”&lt;/td&gt;
					&lt;td&gt;partial read 后未 drain 到 &lt;code&gt;EAGAIN&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;循环读；确认非阻塞；对照 man 管道示例&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单连接阻塞拖死整进程&lt;/td&gt;
					&lt;td&gt;阻塞 fd + 同步 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;全部 &lt;code&gt;O_NONBLOCK&lt;/code&gt;；慢业务丢线程池/协程&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;某连接饿死其它连接&lt;/td&gt;
					&lt;td&gt;单 fd 上无限处理大流量&lt;/td&gt;
					&lt;td&gt;就绪列表 + 轮转；限制单次读写字节/次数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ONESHOT 后永久静音&lt;/td&gt;
					&lt;td&gt;处理完未 &lt;code&gt;EPOLL_CTL_MOD&lt;/code&gt; rearm&lt;/td&gt;
					&lt;td&gt;统一在处理结束路径 rearm&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;对端已关仍不清理&lt;/td&gt;
					&lt;td&gt;只看 &lt;code&gt;EPOLLIN&lt;/code&gt;，忽略挂断&lt;/td&gt;
					&lt;td&gt;处理 &lt;code&gt;EPOLLRDHUP&lt;/code&gt;/&lt;code&gt;EPOLLHUP&lt;/code&gt;/&lt;code&gt;EPOLLERR&lt;/code&gt;，读到 0 关闭&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_ctl ADD&lt;/code&gt; 报 &lt;code&gt;EEXIST&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;重复 ADD 同一 fd&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;MOD&lt;/code&gt; 改掩码，或先 &lt;code&gt;DEL&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_wait&lt;/code&gt; 被信号打断&lt;/td&gt;
					&lt;td&gt;返回 &lt;code&gt;-1&lt;/code&gt; + &lt;code&gt;EINTR&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;循环重试，或使用 &lt;code&gt;epoll_pwait&lt;/code&gt; 管理信号掩码&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多进程抢同一监听 socket&lt;/td&gt;
					&lt;td&gt;惊群/重复唤醒&lt;/td&gt;
					&lt;td&gt;评估 &lt;code&gt;EPOLLEXCLUSIVE&lt;/code&gt;、&lt;code&gt;SO_REUSEPORT&lt;/code&gt; 等策略&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;排障时建议同时抓三份证据：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;fd 是否非阻塞&lt;/strong&gt;（&lt;code&gt;fcntl&lt;/code&gt; 标志）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;interest 掩码&lt;/strong&gt;是否含 &lt;code&gt;EPOLLET&lt;/code&gt;/&lt;code&gt;ONESHOT&lt;/code&gt;/&lt;code&gt;RDHUP&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次事件处理是否读/写到 EAGAIN&lt;/strong&gt;，以及是否处理 hangup。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="七和-selectpoll-的选型建议"&gt;&lt;a href="#%e4%b8%83%e5%92%8c-selectpoll-%e7%9a%84%e9%80%89%e5%9e%8b%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;七、和 select/poll 的选型建议
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;小工具、fd 很少、要极致可移植&lt;/strong&gt;：&lt;code&gt;poll&lt;/code&gt;/&lt;code&gt;select&lt;/code&gt; 或高层封装足够。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux 上万级长连接、自研事件循环&lt;/strong&gt;：epoll（多数生产库默认 ET + 非阻塞）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨 Unix 可移植高性能库&lt;/strong&gt;：用 libuv / netty / Go runtime 等已封装的多路复用层，而不是在业务里手写三套 epoll/kqueue/IOCP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只做业务、不碰事件循环&lt;/strong&gt;：理解 LT/ET 与非阻塞，是为了读懂框架文档和 flame graph，而不是 reinvent。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="八总结"&gt;&lt;a href="#%e5%85%ab%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;八、总结
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;epoll 在内核维护 &lt;strong&gt;interest list + ready list&lt;/strong&gt;，用 &lt;code&gt;create/ctl/wait&lt;/code&gt; 三件套把“注册”和“等待”拆开，从而在大规模 fd 上更省。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认 LT&lt;/strong&gt;：条件成立就通知，写法像 poll；&lt;strong&gt;ET&lt;/strong&gt;：状态变化才通知，必须 &lt;strong&gt;非阻塞 + 读/写到 EAGAIN&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EPOLLONESHOT&lt;/code&gt; 适合多线程串行化处理，但务必 &lt;strong&gt;处理完 rearm&lt;/strong&gt;；&lt;code&gt;EPOLLRDHUP&lt;/code&gt;/&lt;code&gt;HUP&lt;/code&gt;/&lt;code&gt;ERR&lt;/code&gt; 是连接回收的关键信号。&lt;/li&gt;
&lt;li&gt;饥饿、假死、惊群，大多不是“epoll 不灵”，而是 &lt;strong&gt;事件语义与读写协议不匹配&lt;/strong&gt;。先复现 LT/ET 差异，再改生产代码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;掌握这些机制后，再看 Nginx worker、Redis 事件循环、Netty EventLoop 或自研网关，会发现它们都在同一套规则上做工程化封装。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Linux man-pages: &lt;a class="link" href="https://man7.org/linux/man-pages/man7/epoll.7.html" target="_blank" rel="noopener"
 &gt;epoll(7)&lt;/a&gt; — interest/ready、LT/ET、ONESHOT、建议用法与陷阱&lt;/li&gt;
&lt;li&gt;Linux man-pages: &lt;a class="link" href="https://man7.org/linux/man-pages/man2/epoll_ctl.2.html" target="_blank" rel="noopener"
 &gt;epoll_ctl(2)&lt;/a&gt; — 事件标志、ADD/MOD/DEL、EPOLLET/ONESHOT/EXCLUSIVE&lt;/li&gt;
&lt;li&gt;Linux man-pages: &lt;a class="link" href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html" target="_blank" rel="noopener"
 &gt;epoll_wait(2)&lt;/a&gt; — timeout、maxevents、就绪返回语义&lt;/li&gt;
&lt;li&gt;Linux man-pages: &lt;a class="link" href="https://man7.org/linux/man-pages/man2/epoll_create.2.html" target="_blank" rel="noopener"
 &gt;epoll_create(2) / epoll_create1(2)&lt;/a&gt; — 创建实例与 &lt;code&gt;EPOLL_CLOEXEC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Python 3 Docs: &lt;a class="link" href="https://docs.python.org/3/library/selectors.html" target="_blank" rel="noopener"
 &gt;selectors — High-level I/O multiplexing&lt;/a&gt; — &lt;code&gt;EpollSelector&lt;/code&gt; / &lt;code&gt;DefaultSelector&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Nginx Docs: &lt;a class="link" href="https://nginx.org/en/docs/events.html" target="_blank" rel="noopener"
 &gt;Connection processing methods&lt;/a&gt; — Linux 上 epoll 方法及 RDHUP/EXCLUSIVE 支持说明&lt;/li&gt;
&lt;/ol&gt;</description></item></channel></rss>