select,poll和epoll对比分析

博客内容概括了select、poll和epoll三种IO多路复用机制的工作原理及优缺点。epoll在处理大量描述符时表现出更高的效率,因为它采用回调机制,只有文件描述符就绪时才执行回调,避免了轮询的效率问题。

    最近研究python IO多路复用,发现很多案例都是基于selelct,而很少谈到poll和epoll。尽管知道晚出现的epoll的

处理机制更优,但却不明白其中道理。于是将互联网上资料整理总结了一下。


    elect/poll/epoll都是IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作。本质上select/poll/epoll都是同步I/O,即读写是阻塞的。

一、select
原型:

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

    从select函数监控3类文件描述符:writefds、readfds、exceptfds。调用select函数后会阻塞,直到描述符准备就绪(有数据可读、可写、或者出现异常)或者超时,函数便返回。当select函数返回后,可以通过遍历描述符集合,找到就绪的描述符。

select缺点

  • 单进程能够监控的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义增大上限,但同样存在效率低的弱势;
  • IO效随着监视的描述符数量的增长,其效率也会线性下降;
二、poll

原型:

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

其中pollfd表示监视的描述符集合,如下

struct pollfd {
    int fd; //文件描述符
    short events; //监视的请求事件
    short revents; //已发生的事件
};
    pollfd结构包含了要监视的event和发生的event,并且pollfd并没有最大数量限制(但数量过大同样会导致性下降)。 和select函数一样,当poll函数返回后,可以通过遍历描述符集合,找到就绪的描述符。


    从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。


三、epoll
    epoll是在2.6内核中提出的,是select和poll的增强版。相对于select和poll来说,epoll更加灵活,没有描述符数量限制。epoll使用一个文件描述符管理多个描述符,将用户空间的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。epoll机制是Linux最高效的I/O复用机制,在一处等待多个文件句柄的I/O事件。


select/poll都只有一个方法,而epoll的操作过程有3个方法,分别是epoll_create(), epoll_ctl(),epoll_wait()。

3.1 epoll_create()

int epoll_create(int size);

用于创建一个epoll的句柄,size是指监听的描述符个数, 现在内核支持动态扩展,该值的意义仅仅是初次分配的fd个数,后面空间不够时会动态扩容。 当创建完epoll句柄后,占用一个fd值.

ls /proc/<pid>/fd/  //可通过终端执行,看到该fd

使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

3.2 epoll_ctl()

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
用于对需要监听的文件描述符(fd)执行op操作,比如将fd加入到epoll句柄。
  • epfd:是epoll_create()的返回值;
  • op:表示op操作,用三个宏来表示,分别代表添加、删除和修改对fd的监听事件;
  • EPOLL_CTL_ADD(添加)
  • EPOLL_CTL_DEL(删除)
  • EPOLL_CTL_MOD(修改)
  • fd:需要监听的文件描述符;

epoll_event:需要监听的事件,struct epoll_event结构如下:

  struct epoll_event {
    __uint32_t events;  /* Epoll事件 */
    epoll_data_t data;  /*用户可用数据*/
  };
events可取值:(表示对应的文件描述符的操作)
  • EPOLLIN :可读(包括对端SOCKET正常关闭);
  • EPOLLOUT:可写;
  • EPOLLERR:错误;
  • EPOLLHUP:中断;
  • EPOLLPRI:高优先级的可读(这里应该表示有带外数据到来);
  • EPOLLET: 将EPOLL设为边缘触发模式,这是相对于水平触发来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后就不再监听该事件.

3.3 epoll_wait()

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • epfd:等待epfd上的io事件,最多返回maxevents个事件;
  • events:用来从内核得到事件的集合;
  • maxevents:events数量,该maxevents值不能大于创建epoll_create()时的size;
  • timeout:超时时间(毫秒,0会立即返回)。

该函数返回需要处理的事件数目,如返回0表示已超时。


四、对比
    在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。(此处去掉了遍历文件描述符,而是通过监听回调的的机制。这正是epoll的魅力所在。)


epoll优势
    监视的描述符数量不受限制,所支持的FD上限是最大可以打开文件的数目,具体数目可以cat /proc/sys/fs/file-max查看,一般来说这个数目和系统内存关系很大,以3G的手机来说这个值为20-30万。
IO效率不会随着监视fd的数量增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的,只有就绪的fd才会执行回调函数。
如果没有大量的idle-connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle-connection,就会发现epoll的效率大大高于select/poll。

### Linux I/O 复用机制:`select`、`poll` `epoll` 的区别比较 #### 一、基本概念 I/O 多路复用是一种允许单个线程同时监视多个文件描述符的技术,以便在任意一个或多个文件描述符变为可读/写时得到通知。Linux 提供了三种主要的 I/O 多路复用机制:`select`、`poll` `epoll`。 --- #### 二、具体特性对比 1. **`select`** - **特点**: - 支持的最大文件描述符数受限于系统常量(通常为 1024)。可以通过重新编译内核调整上限[^3]。 - 每次调用都需要将所有的文件描述符集合从用户空间拷贝到内核空间,并在线性扫描这些文件描述符以判断其是否准备好[^3]。 - 时间复杂度为 O(n),其中 n 表示要监控的文件描述符总数。 - **性能**: - 在少量文件描述符的情况下表现尚可,但在高并发场景下由于需要频繁地复制扫描大量文件描述符,性能下降明显[^3]。 - **典型应用场景**: - 小规模网络应用或者嵌入式设备上的简单通信程序[^3]。 2. **`poll`** - **特点**: - 解决了 `select` 中最大文件描述符数量限制的问题,理论上可以支持无限多的文件描述符。 - 类似于 `select`,仍然存在每次调用都要将所有感兴趣的文件描述符列表传送到内核的空间开销问题[^3]。 - 同样采用轮询方式检测哪些文件描述符已准备就绪,时间复杂度仍为 O(n)[^3]。 - **性能**: - 相较于 `select` 略有改进,但由于底层实现原理相似,在大规模并发情况下依然不够高效[^3]。 - **典型应用场景**: - 较大但非极高的并发环境下的服务器端编程。 3. **`epoll`** - **特点**: - 基于事件驱动模型设计,专门针对高性能需求而优化[^1]。 - 使用红黑树存储注册过的文件描述符及其关联的状态信息;利用双向链表管理活跃的事件队列[^4]。 - 只需一次性的将文件描述符加入到 epoll 实例中即可,后续无需重复传输整个集合至内核层。 - 支持两种触发模式——边缘触发(Edge Triggered, ET)与水平触发(Level Triggered, LT),提供了更大的灵活性[^1]。 - **性能**: - 即使面对海量连接也能维持较高的效率,因为它只会关注那些确实发生了变化的少数几个文件描述符而不是全部检查一遍[^1]。 - 数据结构的设计使得无论是添加还是查询操作都非常迅速,整体时间复杂度接近于 O(1)[^4]。 - **典型应用场景**: - Web 服务、数据库代理等超高并发的服务端架构。 --- #### 三、总结表格 | 属性 | Select | Poll | Epoll | |--------------|---------------------------------|--------------------------------|-------------------------------| | 文件描述符限 | 默认最多 1024 | 无固定限制 | 几乎不受限 | | 效率 | O(n) | O(n) | 接近 O(1) | | 用户态<->内核态交互频率 | 每次都需传递完整集合 | 每次也都需传递完整集合 | 初始化阶段一次性设置 | | 是否支持多种触发模式 | 否 | 否 | 是 | --- #### 四、代码示例 以下是使用 `epoll` 的一段简化版 C++ 示例代码: ```c++ #include <sys/epoll.h> #include <unistd.h> #define MAX_EVENTS 10 int main() { int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); return 1; } struct epoll_event event; event.events = EPOLLIN; event.data.fd = STDIN_FILENO; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) { perror("epoll_ctl: add"); close(epoll_fd); return 1; } struct epoll_event events[MAX_EVENTS]; while (true) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_pwait"); break; } for(int i = 0;i < nfds; ++i){ printf("Event on fd %d\n",events[i].data.fd ); } } close(epoll_fd); } ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值