epoll和select的区别
Contents
关于 epoll 和 select 的区别, 哪些说法是正确的? (多选)
A. epoll 和 select 都是 I/O 多路复用的技术, 都可以实现同时监听多个 I/O 事件的状态
B. epoll 相比 select 效率更高, 主要是基于其操作系统支持的 I/O 事件通知机制, 而select是基于轮询机制
C. epoll 支持水平触发和边缘触发两种模式
D.select 能并行支持 I/O 比较小, 且无法修改
参考答案: A, B, C
select
什么是select
系统提供select函数来实现多路复用输入/输出模型, 时间复杂度为O(n), select系统调用是用来让我们的程序监听
多个文件描述符的状态变化的, 程序会停在select等待, 直到被监视的文件描述符有一个或多个发生状态的改变
监控原理
- 用户向定义各自自己关心的描述符集合, 将描述符添加到相应的集合中
- 调用select接口, 将集合传入, 会将集合中的数据拷贝到内核中进行监控, 监控原理:
- 在内核中不断进行轮询遍历, 判断哪个集合中有描述符就绪, 包括可读就绪和可写就绪
- 可读就绪: 读缓冲区中, 数据大小大于低水位标记(通常为1个字节)
- 可写就绪: 写缓冲区中, 剩余空间大小大于低水位标记(通常为1个字节)
- 遍历完集合后, 在select调用返回之前, 讲集合所有未就绪的描述符从集合中移除, select返回的集合就是一个就绪描述符集合
- 用户在select调用返回之后虽然无法立即获取就绪的描述符, 但是可以通过判断当前描述符还在集合中来判断描述符就是就绪描述符, 然后进行想用操作
- 因为集合被select在就绪返回前被修改了, 只保留了就绪的描述符, 所以每次重新监控前需要重新添加到描述符集合中去
select的优缺点
优点
- 遵循posix(
可移植操作系统接口
)标准, 拥有良好的跨平台移植性 - 监控时间可以精确到毫秒
缺点
- select所能监控的描述符有最大数量上限, 上线取决于
_FD_SETSIZE
, 默认为1024, 这里的FD_SIZE是在文件里定义的, 不是linux里的那个, 使用sizeof(fd_set)
= 512, 每bit表示一个文件描述符, 则我服务器上支持的最大文件描述符是512*8=4096 - select需要每次将集合数据从用户态
拷贝
到内核态进行监控 - select在内核中使用
轮询遍历
进行监控, 监控性能随着描述符增多
而降低 - select不会直接返回给用户就绪的描述符, 而是
返回一个就绪集合
, 需要用户自己遍历判断才能找到就绪的描述符, 效率较低, 增加了代码复杂度 - select每次调用返回时, 都会
修改集合
, 因此每次监控前都需要重新向集合中添加描述符
poll
什么是poll
监视并等待多个文件描述符的树形变化, 时间复杂度为O(n)
poll 与 select
本质和运行机制和select类似, 没有多大区别, 管理多个描述符也是进行轮询,区别在于, poll没有最大文件描述符数量限制
, 和select存在同样的缺点
, 当大量文件描述符的数组被整体复制于用户态和内核的地址空间之间, 不论这些文件描述符是否就绪, 它的开销都会随着文件描述符的数量的增加而线性增大
epoll
什么是epoll
epoll是基于事件驱动
的I/O方式, 没有描述符个数限制
, 使用一个文件描述符管理多个描述符, 将用户关心的文件描述符的时间存放到内核的一个事件表中, 这样在用户空间和内核空间的copy只需要一次, 时间复杂度O(1)
特点
两种运行方式: ①:ET(edge-triggered)-边缘触发, ②:LT(level-triggered)-水平触发(默认工作方式)
区别:
-
水平触发(LT): 当epoll_wait检测到某描述符事件就绪并通知应用应用程序时, 应用程序可以不立即处理该事件, 下次调用epoll_wait时, 会再次通知此事件
-
边缘触发(ET): 当epoll_wait检测到某个描述符事件就绪并通知应用程序时, 应用程序必须立即处理该事件, 如果不处理, 下次调用epoll_wait时, 不会再次通知此事件了.(直到你做了某些操作改变了该描述符的状态, 从就绪状态改为未就绪状态, 这叫做边缘触发, 状态改变才会通知一次)
可以简单理解为: Level 和 Edge指的是触发点, Level为只要处于水平, 那么就一直触发, , 而Edge则为上升沿和下降沿时触发, 如 0->1为Edge, 1->1为Level
使用场景
epoll是Linux内核为了处理大批量文件描述符而改进的poll, 是Linux下多路复用IO接口select/poll的增强版, 它能显著提升程序在大量并发连接中只有少量活跃的情况下
的系统CPU利用率, 原因是获取事件的时候, 它无需遍历整个被监听的描述符集, 只需要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了
epoll 与select
- 接口使用方便, 虽然拆分成了三个函数, 但是反而使用起来更方便搞笑
- 数据拷贝轻量,是在合适的时候调用EPOLL_CTL_ADD将文件描述符接口拷贝到内核中, 这个操作并不频繁(select/poll都是每次循环都需要进行拷贝)
- 事件回调机制, 避免使用遍历, 而是使用回调函数的方式
- 没有数量的限制, 文件描述符数量无上限
Windows IOCP
什么是IOCP
IOCP全称是I/O Completion Port, 中文翻译为 I/O 完成端口
, IOCP是一个异步
的Windows API, 是Windows平台效率最高, 扩展性最好的IO模型, 针对WinSock的海量连接时, 更能显示出威力, 可以高效的将I/O事件通知给应用程序
IOCP模型属于一种通讯模型
, 适用于WIndows平台下高负载服务器的一个技术, 基于IOCP的开发是异步IO的, 这决定了IOCP所实现的服务器的高吞吐量
完成端口实际上是一个通知队列
, 由操作系统把已经完成的重叠I/O请求的通知放入其中, 当某项I/O操作一旦完成, 某个可以对该操作结果进行处理的工作线程就会收到一项通知, 而套接字在被创建后, 可以在任何时候与某个完成端口进行关联
为什么使用IOCP
在传统模型中, 会出现一个客户端一个线程的问题, 如果软件不是运行在一个多核的情况下, 性能会急剧下降, 线程是系统资源, 并不是无限制的, 也不是低价低廉的
IOCP提供了一种只使用一些(I/O worker)线程去 相对公平的完成多客户端的 输入输出, 如果线程没事干的时候会一直被挂起, 而不会使用CPU时间片, 直到做完为止
如何工作
- 创建一个完成端口(通知队列)
- 根据CPU个数*2创建Worker线程
- 创建监听SOCKET, 把socket和完成端口关联起来, 然后开始在指定的端口上监听连接请求
- 在这个监听Socket上投递AcceptEx请求
- Worker线程处理的事情
- 使用GetQueuedCompletionStatus(阻塞函数)扫描完成端口里是否有网络请求的存在, 有就拿出来处理
- 处理完成再向完成端口投递完成状态
参考: Windows服务器高并发处理IOCP(完成端口)详细说明_波波诸葛伟-CSDN博客_iocp服务器
IOCP 与 epoll
- Epoll是Linux系统下的模型, IOCP是Windows下的模型
- Epoll是当事件资源满足发送可处理的通知消息, IOCP是当事件完成时发出完成通知消息
- Epoll是同步非阻塞, IOCP是异步操作
有一个打印店,有一台打印机,好几个人在排队打印。
普通打印店,正常情况是:
1、你准备好你的文档,来到打印店;
2、排队,等别人打印完;
3、轮到你了,打印你的文档;
4、你取走文档,做后面的处理。
这种方式,你会浪费很多等待时间,非常低效。
于是, Linux和windows都提出了自己最优的模型。
Linux的epoll模型,则可以描述如下:
1、你准备好你的文档,来到打印店;
2、告诉店小二说,我先排队在这位置,轮到我了通知一声(假定你来回路上不耗时);
3、你先去忙你的事情去了;
4、轮到你了,店小二通知你(假定你来回路上不耗时);
5、你获得打印机使用权了,开始打印;
6、打印完了拿走。
你会发现,你节省了排队的时间,等到你能获得打印机资源的时候,告诉你来处理。但是这里,就浪费了一点时间,就是你自己打印。这就是epoll的同步非阻塞。
windows的IOCP模型,则可以描述如下:
1、你准备好你的文档,来到打印店;
2、告诉店小二说,我先排队,轮到我了帮打印下,好了通知我(也假定你来回路上不耗时);
3、你先去忙你的事情去了;
4、轮到你的文档了,店小二直接帮你打印好了,通知你;
5、你来了,直接取走文档。
你会发现,你不但节省了排队时间,你连打印时间都节省了, 完全异步操作。
很显然,IOCP简直是太完美了,可以称得上是最高性能的服务器网络模型了。