select、poll、epoll之间的优缺点

转自: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 的数量的增长而线性增加

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 步骤总结如下:  
  2. 1)先把全部fd扫一遍;  
  3. 2)如果发现有可用的fd,跳到5;  
  4. 3)如果没有,当前进程去睡觉xx秒(schedule_timeout机制);  
  5. 4)xx秒后自己醒了,或者状态变化的fd唤醒了自己,跳到1;  
  6. 5)结束循环体,返回。(注意函数的返回也可能是 timeout 的超时)  

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 的方法还是轮询,如下:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int res = select(maxfd+1, &readfds, NULL, NULL, 120);    
  2. if (res > 0)    
  3. {    
  4.     for (int i = 0; i < MAX_CONNECTION; i++)    
  5.     {    
  6.         if (FD_ISSET(allConnection[i], &readfds))    
  7.         {    
  8.             handleEvent(allConnection[i]);    
  9.         }    
  10.     }    
  11. }    
  12. // if(res == 0) handle timeout, res < 0 handle error   


而 epoll() 只需要从就绪链表中处理就绪的 fd:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int res = epoll_wait(epfd, events, 20, -1);    
  2. for (int i = 0; i < res;i++)    
  3. {    
  4.     handleEvent(events[n]);    
  5. }    

此处的效率对比也是高下立判。




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。


阅读更多

没有更多推荐了,返回首页