转自:http://blog.csdn.net/woxiaohahaa/article/details/51498951
select:
缺点:
1)每次调用select,都存在 fd 集合在用户态与内核态之间的拷贝,I/O 的效率会随着监视 fd 的数量的增长而线性下降。
2)select()调用的内部,需要用轮询的方式去完整遍历每一个 fd,如果遍历完所有 fd 后没有发现就绪 fd,则挂起当前进程,直到有 fd 就绪或者主动超时(使用 schedule_timeout 实现睡一会儿,判断一次(被定时器唤醒,注意与 select() 函数里面的 timeout 参数区分作用)的效果),被唤醒后它又要再次遍历 fd (直到有 fd 就绪或 select() 函数超时)。这个过程经历了多次无谓的遍历。CPU的消耗会随着监视 fd 的数量的增长而线性增加。
3)select支持的文件描述符数量太小了,默认是1024。
4)由于 select 参数输入和输出使用同样的 fd_set ,导致每次 select() 之前都要重新初始化要监视的 fd_set,开销也会比较大。
poll:
poll 的实现和 select 非常相似,它同样存在 fd 集合在用户态和内核态间的拷贝,且在函数内部需要轮询整个 fd 集合。区别于select 的只是描述fd集合的方式不同,poll使用pollfd数组而不是select的fd_set结构,所以poll克服了select文件描述符数量的限制,此外,poll 的 polldf 结构体中分别用 events 和 revents 来存储输入和输出,较之 select() 不用每次调用 poll() 之前都要重新初始化需要监视的事件。
epoll:
epoll是一种 Reactor 模式,提供了三个函数,epoll_create(),epoll_ctl() 和 epoll_wait()。
优点:
1)对于上面的第一个缺点,epoll 的解决方案在 epoll_ctl() 函数中。每次注册新的事件到 epoll 描述符中时,会把该 fd 拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll 保证了每个fd在整个过程中只会拷贝一次。
2)对于第二个缺点,epoll 的解决方案不像 select 或 poll 一样轮询 fd,而只在 epoll_ctl 时把要监控的 fd 挂一遍,并为每个 fd 指定一个回调函数,当设备就绪,这个回调函数把就绪的 fd 加入一个就绪链表。epoll_wait 的工作实际上就是在这个就绪链表中查看有没有就绪的 fd,也即 epoll_wait 只关心“活跃”的描述符,而不用像 select() 和 poll() 需要遍历所有 fd,它需要不断轮询就绪链表,期间也可能多次睡眠和唤醒(类似与 select, poll),但终究它的轮询只用判断就续表是否为空即可,其CPU的消耗不会随着监视 fd 的数量的增长而线性增加,这就是回调机制的优势,也正是 epoll 的魅力所在。
同理,select() 和 poll() 函数返回后, 处理就绪 fd 的方法还是轮询,如下:
而 epoll() 只需要从就绪链表中处理就绪的 fd:
此处的效率对比也是高下立判。
3)对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个和系统限制有关,linux里面可以用ulimit查看文件打开数限制。
缺点:epoll是 linux 特有的,而 select 和 poll 是在 POSIX 中规定的,跨平台支持更好。
综上:
select 、poll、epoll 的使用要根据具体的使用场合,并不是 epoll 的性能就一定好,因为回调函数也是有消耗的,当 socket 连接较少时或者是即使 socket 连接很多,但是连接基本都是活跃的情况下,select / poll 的性能与 epoll 是差不多的。即如果没有大量的 idle-connection 或者 dead-connection,epoll 的效率并不会比 select/poll 高很多,但是当遇到大量的 idle-connection,就会发现epoll 的效率大大高于 select/poll。