epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现。
网上现在关于这两者不同的介绍已经到处都是了。我这里也不能多说出什么东西,只是记录下我看了实现代码之后的一些总结。
两者的使用场景一般是通过一个入口能够同时监控多路I/O。一般使用的接口,
epool就是
- int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
select为:
- int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
通过上述两个函数能够将调用线程阻塞,线程变为可执行条件有两种情况:
- 无任何事件发生,超时时间已过
- 在所控制的I/O有事件到来
epoll_wait函数中看不到相关的监控信息,因为是通过epoll_ctl已经加入,而select之间在函数调用中由(fd_set *readfds, fd_set *writefds, fd_set *exceptfds)传入。epoll_wait饭互结果通过events返回,而select的传入参数也是传出参数。两者传出参数均表示发生事件的对应I/O标识。
两种方式的区别主要体现在以下几个方面:
- select所能控制的I/O数有限,这主要是因为fd_set数据结构是一个有大小的,相当与一个定长所数组。
- select每次都需要重新设置所要监控的fd_set(因为调用之后会改变其内容),这增加了程序开销。
- select的性能要比epoll差,具体原因会在后续内容中详细说明。
嗯,说道这个为什么select要差,那就要从这个select API说起了。这个传进去一个数组,内部实现也不知道那个有哪个没有,所以要遍历一遍。假设说我只监控一个文件描述符,但是他是1000。那么select需要遍历前999个之后再来poll这个1000的文件描述符,而epoll则不需要,因为在之前epoll_ctl的调用过程中,已经维护了一个队列,所以直接等待事件到来就可以了。
Linux中select此段相关代码为:
- /* 遍历所有传入的fd_set */
- for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
- unsigned long in, out, ex, all_bits, bit = 1, mask, j;
- unsigned long res_in = 0, res_out = 0, res_ex = 0;
- const struct file_operations *f_op = NULL;
- struct file *file = NULL;
-
- in = *inp++; out = *outp++; ex = *exp++;
- all_bits = in | out | ex;
- /* 此处跳无需监控的fd, 白白的浪费时间啊…… */
- if (all_bits == 0) {
- i += __NFDBITS;
- continue;
- }
- /* 后续进行一些相关操作 */
-
}
而epoll则无需进行此类操作,直接检测内部维护的一个就绪队列,如果队列有内容,说明有I/O就绪,那么直接赋值返回内容,成功返回,如果没有成功,那么睡眠,等待就绪队列非空。
通过这个两者的比较,其实两者的差距啊,大部分是因为这个API设计所决定的,select就设计成这样一个API,内部再怎么优化也只能是这么个烂样子,而epoll这样维护与等待分离,灵活多变,最后也就带来了相对的高性能,以及可扩展性。
以后的代码生涯中,还是先要设计好API,然后才能写出好代码……
阅读(13075) | 评论(1) | 转发(1) |