概念概述
epoll 与 kqueue 的 I/O 多路复用(I/O Multiplexing with epoll and kqueue)指的是:I/O 多路复用同时等待大量文件描述符,仅在某个就绪时通知程序,是单线程服务处理数千套接字的关键机制。
从更严格的定义看,I/O multiplexing is a kernel facility that lets a program block on a set of file descriptors and be told which ones are ready for reading or writing, rather than checking each in turn. 该概念在工程实践中的价值,在于它能帮助团队用统一的语言描述一个具体的质量属性或设计约束,从而在评审与协作中减少歧义。
为什么需要它
It is the mechanism behind event-driven servers and runtimes on Linux and BSD systems. 它与 Atomic Operations、Seqlocks、Lock Fairness and Starvation 等概念经常一起出现,用于解决相近的工程问题。理解它的适用场景,有助于在多种备选方案之间做出有依据的取舍,而不是凭直觉选择技术方案。
常见误解
- multiplexing performs the reads and writes on the caller’s behalf
- select, poll, and epoll have identical scaling behaviour
需要避免的反模式
- using level-triggered notifications without draining a socket
- building an event loop that does not handle partial reads
实践要点
- Epoll and kqueue register interest once and report only ready descriptors, avoiding the linear scan that select and poll perform on every call.
- Edge-triggered mode reports readiness transitions rather than current state, so a handler must drain the socket until it would block or it will miss the next event.
- Readiness guarantees only that a small read will not block, so code must still loop on partial reads and handle end of stream explicitly.
参考来源
https://man7.org/linux/man-pages/man7/epoll.7.html
代码示例
export function createSemaphore(permits) {
let available = permits;
const waiters = [];
return {
async acquire() {
if (available > 0) {
available--;
return () => release();
}
await new Promise((resolve) => waiters.push(resolve));
return () => release();
},
};
function release() {
const next = waiters.shift();
if (next) next();
else available++;
}
}