一文说明不同IO多路复用 select、poll、epoll 和 kqueue ?不信你还看不懂!

我们以餐厅服务员模型类比,并结合代码片段说明 selectpollepollkqueue 的工作方式。


场景设定

假设一个餐厅有 10张餐桌(对应10个IO事件),服务员需要监听哪些餐桌有客人需要服务(如点菜、结账)。目标是高效完成所有需求。


1. select:轮询所有餐桌

工作方式
  • 服务员(线程)拿着一张固定大小的清单(默认最多1024个文件描述符),挨个询问每张餐桌:“需要服务吗?”。
  • 如果某张餐桌举手(数据就绪),服务员标记它并处理需求。
代码片段(伪代码)
// 1. 初始化监听清单(fd_set)
fd_set read_fds;
FD_ZERO(&read_fds);
for (餐桌 in 所有餐桌) { FD_SET(餐桌, &read_fds); }

// 2. 等待事件:遍历所有餐桌,检查是否有需求
int ready = select(max_fd+1, &read_fds, NULL, NULL, timeout);
if (ready > 0) {
    for (餐桌 in 所有餐桌) {
        if (FD_ISSET(餐桌, &read_fds)) {  // 逐个检查
            处理需求(餐桌);
        }
    }
}
缺点
  • 清单大小受限:最多只能监听1024张餐桌(受 FD_SETSIZE 限制)。
  • 重复遍历:即使只有1张餐桌需要服务,也要遍历所有10张餐桌。

2. poll:改进的轮询

工作方式
  • 服务员用一张无限制的清单(链表结构),仍然挨个询问每张餐桌。
  • 解决了 select 的餐桌数量限制问题。
代码片段(伪代码)
// 1. 定义监听结构体数组
struct pollfd fds[10];
for (i=0; i<10; i++) {
    fds[i].fd = 餐桌[i];
    fds[i].events = POLLIN;  // 监听可读事件
}

// 2. 等待事件
int ready = poll(fds, 10, timeout);
if (ready > 0) {
    for (i=0; i<10; i++) {
        if (fds[i].revents & POLLIN) {  // 遍历所有餐桌
            处理需求(fds[i].fd);
        }
    }
}

 

缺点

  • 效率问题:和 select 一样,仍需遍历所有餐桌,只是突破了数量限制。

3. epoll(Linux专属):事件驱动

工作方式
  • 服务员通过一个智能通知系统(内核事件表)监听餐桌。
  • 当某张餐桌需要服务时,系统直接告诉服务员是哪几张餐桌(仅返回就绪的列表)。
代码片段(伪代码)
// 1. 创建epoll实例(相当于服务员拿到智能通知器)
int epoll_fd = epoll_create1(0);

// 2. 注册餐桌到通知器
struct epoll_event event;
for (餐桌 in 所有餐桌) {
    event.events = EPOLLIN;
    event.data.fd = 餐桌;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 餐桌, &event);
}

// 3. 等待事件:系统直接返回就绪的餐桌列表
struct epoll_event ready_events[10];
int ready = epoll_wait(epoll_fd, ready_events, 10, timeout);
for (i=0; i<ready; i++) {  // 仅遍历就绪的餐桌
    处理需求(ready_events[i].data.fd);
}
优势
  • 事件驱动:无需遍历所有餐桌,时间复杂度O(1)。
  • 支持两种模式
    • 水平触发(LT):只要餐桌有需求,持续通知服务员。
    • 边缘触发(ET):仅在餐桌需求状态变化时通知一次(需一次处理完所有需求)。

4. kqueue(BSD/macOS专属)

工作方式
  • 类似 epoll,但用于BSD系统(如macOS)。
  • 服务员通过一个事件过滤器监听餐桌,支持更复杂的事件类型(如文件修改、信号等)。
代码片段(伪代码)
// 1. 创建kqueue实例
int kq = kqueue();

// 2. 注册餐桌事件
struct kevent events[10];
for (i=0; i<10; i++) {
    EV_SET(&events[i], 餐桌[i], EVFILT_READ, EV_ADD, 0, 0, NULL);
}
kevent(kq, events, 10, NULL, 0, NULL);

// 3. 等待事件:直接获取就绪的餐桌列表
struct kevent ready_events[10];
int ready = kevent(kq, NULL, 0, ready_events, 10, NULL);
for (i=0; i<ready; i++) {
    处理需求(ready_events[i].ident);
}

总结对比

机制工作方式适用系统性能核心缺点
select遍历所有描述符跨平台低(O(n))数量限制,效率低
poll遍历所有描述符(链表)跨平台低(O(n))效率随数量增加下降
epoll事件驱动,仅处理就绪的描述符Linux高(O(1))仅支持Linux
kqueue事件驱动,支持复杂事件BSD/macOS高(O(1))仅支持BSD系系统

如何选择?

  • Linux高并发场景:优先用 epoll(如Nginx、Redis)。
  • 跨平台需求:用 selectpoll(如小型服务器)。
  • BSD/macOS系统:用 kqueue
  • 超大规模连接epoll/kqueue 的事件驱动模型是唯一选择。

关键理解

  • select/poll 是“轮询”模式:无论有没有需求,都要问一遍所有餐桌。
  • epoll/kqueue 是“通知”模式:只有需要服务的餐桌会主动上报,服务员直接处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

There Is No Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值