9. select、poll、epoll 如何选择
linux 提供了 select、poll、epoll 等不同 IO 多路服务用技术来支持高性能的 Web Server 场景,但是它们之间的本质区别在哪里,我们又该如何选择?
9.1 select
select 是 linux 中早期的 IO 多路复用技术,受限于当时的技术场景和要求,select 的在设计之初它的一些特性就无法满足现代互联网高性能 Web Server 的要求:
- select 是通过 fd_set 来表示需要监听的文件描述符的,即一个整数数组,数组的每一个数字的一位表示一个我们感兴趣的文件描述符,调用 select 需要传递我们感兴趣的文件描述符,它们分别组成了 readset、writeset 和 exceptset ,由于 select 的系统调用会修改这些 fd_set,因为它们既是参数,又是结果集,这意味着即使只有一个文件描述符有事件要处理,整个 fd_set 都需要在内核和应用程序之间进行拷贝
- 如果想要知道哪一个文件描述符有事件需要处理,需要对整个 fd_set 进行迭代依次做检查,即使仅仅只有一个文件描述符有事件要处理,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用 select 却会对所有 socket 进行一次线性扫描
- select 支持的最大文件描述符是通过常量 FD_SETSIZE 控制的,而这个常量在 linux 编译时才能修改和确定,意味着 select 有最大文件描述符限制,通常是 1024,如果想要修改这个值,需要重新对 linux 内核进行编译
- select 对多线程不友好
select 有这么多缺点,是不是应该将之送进计算机历史博物馆了,有什么理由需要使用 select 么?
理由一
select 有很好的跨平台特性,由于 select 诞生非常早,select 在大部分平台都得到了支持。
理由二
select 处理 timeout 时的精度要比 poll 和 epoll 要高,如果在某些场景需要更高精度的 timeout 控制,select 是个更好的选择,比如实时性要求很高的场景。
9.2 poll
poll 的出现弥补了 select 自身的一些缺陷,它支持并包括:
- 没有 select 监听文件描述符大小的限制
- poll 不会修改传给它的 pollfd ,意味着可以重用每次文件描述符集
- poll 对各种 event 的支持更广泛更精细
但是 poll 同样也有自身的缺点:
- 类似 select,为了找出哪个文件描述符有事件需要处理,poll 需要遍历所有文件描述符
- 类似 select,poll 不支持动态修改监听中的文件描述符事件
poll 用在什么场景更合适?
场景一
poll 的跨平台特性比较好,如果跨平台是必须要有的,可以考虑 poll。
场景二
同时需要监听的 socket 少于 1000 ,可以考虑使用 poll。
场景三
同时需要监听的 socket 少于 1000 ,而且这些连接大部分都是短连接。
场景四
没有动态修改事件的需要。
9.3 epoll
epoll 是现代高性能网络应用程序的代表,它支持并包括:
- epoll 只会返回已经触发事件的文件描述符,不需要迭代所有的文件描述符
- epoll 可以动态的修改事件,支持在添加和移除监听文件描述符
- epoll 在多线程环境下支持良好
epoll 的高效同样也意味着 epoll 是要比 poll 和 select 都更复杂的 IO 多路复用技术,而且更多的特性也就是意味着更多的系统调用开销,比如每次修改事件都需要一次系统调用,由于 epoll 在获取一个新的 connection 时需要两次系统调用,所以在需要处理大量短连接的场景下 epoll 可能会比 poll 花费更多的时间,epoll 仅仅在 linux 平台可用,即便如此,epoll 也是编写高性能网络应用程序的首选,尤其是应用程序是以下情形:
- 需要处理大量连接
- 连接中包括大量的长连接
9.4 最简单的选择
当你下次编写网络应用程序面对 select、poll、epoll 几种 IO 多路复用技术犹豫不决时,除非你真的很确定需要的是 select 或 poll,否则总是选 epoll 就是对的。