- select:
- 优点:
- 跨平台性好,几乎所有的操作系统都支持
select
。 - 简单易用,接口简洁,易于理解和上手。
- 缺点:每次调用select后,内核会清除集合中未就绪的文件描述符,不会保存,每次调用select前都要重新设置一次集合
- 效率较低,
select
在处理大量文件描述符时性能下降明显,因为它采用线性扫描的方式来遍历文件描述符集合。 - select 调用需要传入 fd 数组,每次调用select需要拷贝一份到内核,每次调用select前都要重新将fd加入文件描述符集合中,因为事件发生后,文件描述符集合将被内核修改,高并发场景下这样的拷贝消耗的资源是惊人的。
- select 在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。
- select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。
- 对于大量的文件描述符,需要维护大型的数据结构,会带来额外的开销。
- 文件描述符集合的大小有限制,通常为
FD_SETSIZE
,1024,在一些系统中可能会受到限制。
- poll:
- 优点:
- 跨平台性好,几乎所有的操作系统都支持
poll
。 - 比
select
更加灵活,没有文件描述符集合大小的限制。 - 对于大量的文件描述符,性能相对较好,因为
poll
使用链表来管理文件描述符,遍历开销相对较小。
- 缺点:
- 在大量文件描述符的情况下,性能仍然有限,因为每次调用
poll
都需要将所有的文件描述符从用户空间复制到内核空间,然后再从内核空间复制回来。
- epoll:
- 优点:
- 高性能,适用于大量的并发连接,因为
epoll
使用了红黑树来管理文件描述符,可以快速定位到就绪事件。 - 内核中保存一份文件描述符集合(红黑树结构),无需用户每次都重新传入,只需进行增删改即可。
- 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。
- 支持水平触发和边缘触发两种模式,边缘触发模式下只会触发一次就绪事件,避免了反复触发事件的开销。
- 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。
- 没有文件描述符集合的大小限制,可以处理大量的并发连接。
- 缺点:
- 不能跨平台,
epoll
是 Linux 特有的 API,不太容易移植到其他操作系统上。 - 使用较为复杂,相比于
select
和poll
,epoll
的接口更加底层,需要更多的编程技巧和经验。
应用场景
很容易产生一种错觉认为只要用 epoll 就可以了,select 和 poll 都已经过时了,其实它们都有各自的使用场景。
select 应用场景
select 的 timeout 参数精度为微秒,而 poll 和 epoll 为毫秒,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。
select 可移植性更好,几乎被所有主流平台所支持。
poll 应用场景
poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。
epoll 应用场景
只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。
需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。
需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。