select和epoll都是监控文件句柄的IO动作是否准备就绪的实现方式,区别在于才大多数情况下后者比前者更加高效。之所以造成这种情况,是由于两者的实现机制不同。
1 select监控句柄IO动作的方式
select内部维护了一个数组,这个数据中记录的都是文件句柄。select通过遍历这个数据,来监控有哪些句柄的IO动作已经准备就绪。
1 在遍历数组过程中,如果发现有句柄准备就绪,那么就会返回,通知程序已经有句柄准备就绪,可供读写,但是这里并没有告诉程序是哪个句柄准备就绪,要想获取,需要程序再次遍历这个数组;
2 如果select遍历完真个数组,都没有就绪的句柄,那么select进程就会沉睡
3 在select陷入沉睡的过程中,当数组中的句柄有准备就绪的话,就会去唤醒select,select唤醒之后,会再次遍历数组,找寻就绪的句柄
通过以上步骤,可以发现,基于select来监控句柄的状态,其时间复杂度是O(n),这还不算其等待时间
另外,linux规定单进程能够监控的句柄最多是1024个,这也就意味着当需要监控超过1024个句柄时,就需要多个select进程,而这种情况又是非常常见的,如Web服务器需要维护数万级别的Socket连接,因此,在这种情况下select的性能比较低下
2 epoll
epoll完美规避了select所存在的问题
1 首先,epoll为了监控句柄,内部并非维护一个数组,而是维护了一个链表,链表大小没有限制,因此一个epoll进程可以监控远远超过1024个句柄。
2 这个链表是按照红黑树的数据结构进行存储的,这就意味着查找和删除里面的句柄非常迅速
3 除此之外,epoll内部还维护了额外的一个链表,用于记录准备就绪的句柄。当程序调用epoll_wait时,只需要检查这个链表是否为空即可,不为空,即返回这个链表,里面就是所有准备就绪的句柄。
通过上面可以看出,首先epoll的单进程既可以监控大规模数量的句柄,然后,epoll监控的是一个红黑树,这移除一个句柄和增加一个监控句柄非常容易,最后,程序获取准备就绪的句柄时,无须遍历所有监控句柄,只需要调用epoll_wait就可获取所有准备就绪的句柄,因此非常高效。
但是,需要注意的是,并非所有情况下,epoll效率都比select高,当监控的句柄中每次查询都是绝大部分处于就绪状态的话,那么使用select反而比epoll效率高,这事因为如果监控的绝大多数句柄都处于就绪状态的话,epoll的就绪链表就会很大,寻找这个链表的效率就会很低