select、poll、epoll的比较

I/O复用是指将要监听的多个文件描述符集中起来,当其中有描述符就绪时(可读,可写,异常等),可以对多个描述符进行操作,而不用阻塞在一个描述符上。select、poll、epoll都是I/O复用的一种实现。它们监听多个文件描述符,直到一个或多个描述符上有事件发生时返回。

从以下几个方面来比较这三种方式。

1、事件集

这三种方式都通过某种结构体变量告诉内核要监听哪些文件描述符上的哪些事件。

select使用fd_set类型,它仅仅是一个描述符集合,没有事件的信息,因此需要提供3组fd_set来注册不同的事件,可读,可写,异常。这使得select不能处理更多的事件,另一方面由于每次循环内核对fd_set进行修改,程序再次调用select前需要重置这3组fd_set。

poll使用pollfd类型,它将文件描述符和事件类型都定义在其中,任何事件都被统一处理。且pollfd有两个成员events和revents,内核每次修改的是revents,events保持不变,这样不需要每次调用poll之前再重置将事件集。poll可以看成是对select的改进,使得编程接口简洁许多。

select和poll返回的事件集都是整个事件集。

epoll采用的方式与select和poll不同,它在内核中维护一个事件表,epoll_ctl函数负责向事件表中添加、删除、修改事件,它需要一个额外的文件描述符指向这个事件表,这样每次调用epoll之后,内核会直接返回就绪的文件描述符,而不是所有描述符。

2、索引就绪文件描述符的时间复杂度

select和poll返回的都是所有文件描述符,因此索引的复杂度为O(n),而epoll只是返回就绪的文件描述符,故复杂度为O(1)。

3、实现原理

select和poll都是采用轮询的方式来检测描述符是否就绪,即扫描整个文件描述符集合,因此检测的时间复杂度为O(n),而epoll采用的是回调的方式,内核检测到就绪文件描述符时,会触发回调函数,回调函数将该文件描述符上的事件插入内核就绪事件队列,最后内核在适当的时机将事件队列中的事件拷贝到用户空间,检测的复杂度为O(1),因此epoll不需要对整个集合进行扫描,由内核来处理就绪的事件。

但是当活动连接较多时,回调函数触发过于频繁,此时epoll的效率未必比select和poll高,因此epoll适合连接数量多,活动连接较少的情况。此外epoll是linux系统独有的,如果考虑到跨平台,select、poll更适合。

分享到