这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
select/poll/epoll对比:
注意:遍历相当于查看所有的位置,回调相当于查看对应的位置。
1 select
POSIX所规定,目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理
缺点
- 单个进程可监视的fd数量被限制,即能监听端口的数量有限,数值存在如下文件里:
cat /proc/sys/fs/file-max
- 对socket是线性扫描,即采用轮询的方法,效率较低
- select采取了内存拷贝方法来实现内核将FD消息通知给用户空间,这样一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
select是第一版IO复用,提出后暴漏了很多问题。
- select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的
- select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但不会告诉是那个sock上有数据,只能自己遍历查找
- select 只能监视1024个链接
- select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现这个sock不用,要收回,这个select 不支持的
2 poll
本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态
- 其没有最大连接数的限制,原因是它是基于链表来存储的
- 大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义
- poll特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd
- 边缘触发:只通知一次,epoll用的就是边缘触发
poll 修复了 select 的很多问题:
- poll 去掉了1024个链接的限制
- poll 从设计上来说不再修改传入数组
但是poll仍然不是线程安全的, 这就意味着不管服务器有多强悍,你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了,不过然后你就有了多进程的各种问题。
3 epoll
在Linux2.6内核中提出的select和poll的增强版本
- 支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次
- 使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知
优点
- 没有最大并发连接的限制:能打开的FD的上限远大于1024(1G的内存能监听约10万个端口)
- 效率提升:非轮询的方式,不会随着FD数目的增加而效率下降;只有活跃可用的FD才会调用callback函数,即epoll最大的优点就在于它只管理“活跃”的连接,而跟连接总数无关
- 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销
- 文件映射内存直接通过地址空间访问,效率更高,把文件映射到内存中
epoll 可以说是 I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:
- epoll 现在是线程安全的
- epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了
- epoll 内核态管理了各种IO文件描述符, 以前用户态发送所有文件描述符到内核态,然后内核态负责筛选返回可用数组,现在epoll模式下所有文件描述符在内核态有存,查询时不用传文件描述符进去了