基础编程学习快乐每一天
首页
留言
Siddim.com
当前位置:
首页
>
编程知识库
>
后端开发知识
>
一道搜狗面试题:IO多路复用中select、poll、epoll之间的区别
一道搜狗面试题:IO多路复用中select、poll、epoll之间的区别
阅读
1
2020-07-13
: 小
Flag
实现,
(1)select==>时间复杂度O(n)
它仅仅知道了,有
I
/
O
事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以
select
具有
O
(
n
)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
(2)poll==>时间复杂度O(n)
poll
本质上和
select
没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个
fd
对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
(3)epoll==>时间复杂度O(1)
epoll
可以理解为
event
poll
,不同于忙轮询和无差别轮询,
epoll
会把哪个流发生了怎样的
I
/
O
事件通知我们。所以我们说
epoll
实际上是事件驱动(每个事件关联上
fd
)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了
O
(
1
))
select
,
poll
,
epoll
都是
IO
多路复用的机制。
I
/
O
多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但
select
,
poll
,
epoll
本质上都是同步
I
/
O
,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步
I
/
O
则无需自己负责进行读写,异步
I
/
O
的实现会负责把数据从内核拷贝到用户空间。
epoll
跟
select
都能提供多路
I
/
O
复用的解决方案。在现在的
Linux
内核里有都能够支持,其中
epoll
是
Linux
所特有,而
select
则应该是
POSIX
所规定,一般操作系统均有实现
select:
select
本质上是通过设置或者检查存放
fd
标志位的数据结构来进行下一步处理。这样所带来的缺点是:
1
、 单个进程可监视的
fd
数量被限制,即能监听端口的大小有限。
一般来说这个数目和系统内存关系很大,具体数目可以
cat
/
proc
/
sys
/
fs
/
file
-
max
察看。
32
位机默认是
1024
个。
64
位机默认是
2048
.
2
、 对
socket
进行扫描时是线性扫描,即采用轮询的方法,效率较低:
当套接字比较多的时候,每次
select
()都要通过遍历
FD
_
SETSIZE
个
Socket
来完成调度,不管哪个
Socket
是活跃的,都遍历一遍。这会浪费很多
CPU
时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是
epoll
与
kqueue
做的。
3
、需要维护一个用来存放大量
fd
的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
poll:
poll
本质上和
select
没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个
fd
对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有
fd
后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历
fd
。这个过程经历了多次无谓的遍历。
它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:
大量的
fd
的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
poll
还有一个特点是“水平触发”,如果报告了
fd
后,没有被处理,那么下次
poll
时会再次报告该
fd
。
epoll:
epoll
有
EPOLLLT
和
EPOLLET
两种触发模式,
LT
是默认的模式,
ET
是“高速”模式。
LT
模式下,只要这个
fd
还有数据可读,每次
epoll
_
wait
都会返回它的事件,提醒用户程序去操作,而在
ET
(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论
fd
中是否还有数据可读。
所以在
ET
模式下,
read
一个
fd
的时候一定要把它的
buffer
读光,也就是说一直读到
read
的返回值小于请求值,或者 遇到
EAGAIN
错误。还有一个特点是,
epoll
使用“事件”的就绪通知方式,通过
epoll
_
ctl
注册
fd
,一旦该
fd
就绪,内核就会采用类似
callback
的回调机制来激活该
fd
,
epoll
_
wait
便可以收到通知。
epoll为什么要有EPOLLET触发模式?
如果采用
EPOLLLT
模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用
epoll
_
wait
都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用
EPOLLET
这种边沿触发模式的话,当被监控的文件描述符上有可读写事件发生时,
epoll
_
wait
()会通知处理程序去读写。
如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用
epoll
_
wait
()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符
epoll
的优点:
没有最大并发连接的限制,能打开的
FD
的上限远大于
1024
(
1G
的内存上能监听约
10
万个端口);
效率提升,不是轮询的方式,不会随着
FD
数目的增加效率下降。只有活跃可用的
FD
才会调用
callback
函数;即
Epoll
最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,
Epoll
的效率就会远远高于
select
和
poll
。
内存拷贝,利用
mmap
()文件映射内存加速与内核空间的消息传递;即
epoll
使用
mmap
减少复制开销。
select、poll、epoll 区别总结:
1、支持一个进程所能打开的最大连接数
select
单个进程所能打开的最大连接数有
FD
_
SETSIZE
宏定义,其大小是
32
个整数的大小(在
32
位的机器上,大小就是
3232
,同理
64
位机器上
FD
_
SETSIZE
为
3264
),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。
poll
poll
本质上和
select
没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的
epoll
虽然连接数有上限,但是很大,
1G
内存的机器上可以打开
10
万左右的连接,
2G
内存的机器可以打开
20
万左右的连接
2、FD剧增后带来的IO效率问题
select
因为每次调用时都会对连接进行线性遍历,所以随着
FD
的增加会造成遍历速度慢的“线性下降性能问题”。
poll
同上
epoll
因为
epoll
内核中实现是根据每个
fd
上的
callback
函数来实现的,只有活跃的
socket
才会主动调用
callback
,所以在活跃
socket
较少的情况下,使用
epoll
没有前面两者的线性下降的性能问题,但是所有
socket
都很活跃的情况下,可能会有性能问题。
3、 消息传递方式
select
内核需要将消息传递到用户空间,都需要内核拷贝动作
poll
同上
epoll
epoll
通过内核和用户空间共享一块内存来实现的。
总结:
综上,在选择
select
,
poll
,
epoll
时要根据具体的使用场合以及这三种方式的自身特点。
1
、表面上看
epoll
的性能最好,但是在连接数少并且连接都十分活跃的情况下,
select
和
poll
的性能可能比
epoll
好,毕竟
epoll
的通知机制需要很多函数回调。
2
、
select
低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善
今天对这三种
IO
多路复用进行对比,参考网上和书上面的资料,整理如下:
1、select实现
select
的调用过程如下所示:
使用
copy
_
from
_
user
从用户空间拷贝
fd
_
set
到内核空间
注册回调函数__
pollwait
遍历所有
fd
,调用其对应的
poll
方法(对于
socket
,这个
poll
方法是
sock
_
poll
,
sock
_
poll
根据情况会调用到
tcp
_
poll
,
udp
_
poll
或者
datagram
_
poll
) -以
tcp
_
poll
为例,其核心实现就是__
pollwait
,也就是上面注册的回调函数。
__
pollwait
的主要工作就是把
current
(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于
tcp
_
poll
来说,其等待队列是
sk
-&
gt
;
sk
_
sleep
(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时
current
便被唤醒了。
poll
方法返回时会返回一个描述读写操作是否就绪的
mask
掩码,根据这个
mask
掩码给
fd
_
set
赋值。
如果遍历完所有的
fd
,还没有返回一个可读写的
mask
掩码,则会调用
schedule
_
timeout
是调用
select
的进程(也就是
current
)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(
schedule
_
timeout
指定),还是没人唤醒,则调用
select
的进程会重新被唤醒获得
CPU
,进而重新遍历
fd
,判断有没有就绪的
fd
。
把
fd
_
set
从内核空间拷贝到用户空间。
往期
100
期面试题汇总
总结:
select
的几大缺点:
每次调用
select
,都需要把
fd
集合从用户态拷贝到内核态,这个开销在
fd
很多时会很大
同时每次调用
select
都需要在内核遍历传递进来的所有
fd
,这个开销在
fd
很多时也很大
select
支持的文件描述符数量太小了,默认是
1024
2、poll实现
poll
的实现和
select
非常相似,只是描述
fd
集合的方式不同,
poll
使用
pollfd
结构而不是
select
的
fd
_
set
结构,其他的都差不多,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是
poll
没有最大文件描述符数量的限制。
poll
和
select
同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
3、epoll
epoll
既然是对
select
和
poll
的改进,就应该能避免上述的三个缺点。那
epoll
都是怎么解决的呢?在此之前,我们先看一下
epoll
和
select
和
poll
的调用接口上的不同,
select
和
poll
都只提供了一个函数——
select
或者
poll
函数。
而
epoll
提供了三个函数,
epoll
_
create
,
epoll
_
ctl
和
epoll
_
wait
,
epoll
_
create
是创建一个
epoll
句柄;
epoll
_
ctl
是注册要监听的事件类型;
epoll
_
wait
则是等待事件的产生。
对于第一个缺点,
epoll
的解决方案在
epoll
_
ctl
函数中。每次注册新的事件到
epoll
句柄中时(在
epoll
_
ctl
中指定
EPOLL
_
CTL
_
ADD
),会把所有的
fd
拷贝进内核,而不是在
epoll
_
wait
的时候重复拷贝。
epoll
保证了每个
fd
在整个过程中只会拷贝一次。
对于第二个缺点,
epoll
的解决方案不像
select
或
poll
一样每次都把
current
轮流加入
fd
对应的设备等待队列中,而只在
epoll
_
ctl
时把
current
挂一遍(这一遍必不可少)并为每个
fd
指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的
fd
加入一个就绪链表)。
epoll
_
wait
的工作实际上就是在这个就绪链表中查看有没有就绪的
fd
(利用
schedule
_
timeout
()实现睡一会,判断一会的效果,和
select
实现中的第
7
步是类似的)。
对于第三个缺点,
epoll
没有这个限制,它所支持的
FD
上限是最大可以打开文件的数目,这个数字一般远大于
2048
,举个例子,在
1GB
内存的机器上大约是
10
万左右,具体数目可以
cat
/
proc
/
sys
/
fs
/
file
-
max
察看,一般来说这个数目和系统内存关系很大。
总结:
(
1
)
select
,
poll
实现需要自己不断轮询所有
fd
集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而
epoll
其实也需要调用
epoll
_
wait
不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪
fd
放入就绪链表中,并唤醒在
epoll
_
wait
中进入睡眠的进程。
虽然都要睡眠和交替,但是
select
和
poll
在“醒着”的时候要遍历整个
fd
集合,而
epoll
在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的
CPU
时间。这就是回调机制带来的性能提升。
(
2
)
select
,
poll
每次调用都要把
fd
集合从用户态往内核态拷贝一次,并且要把
current
往设备等待队列中挂一次,而
epoll
只要一次拷贝,而且把
current
往等待队列上挂也只挂一次(在
epoll
_
wait
的开始,注意这里的等待队列并不是设备等待队列,只是一个
epoll
内部定义的等待队列)。这也能节省不少的开销。
参考
https
://
www
.
cnblogs
.
com
/
zhaodahai
/
p
/
6831456
.
html
https
://
www
.
cnblogs
.
com
/
sky
-
heaven
/
p
/
7011684
.
html
作者:至尊宝
cnblogs
.
com
/
aspirant
/
p
/
9166944
.
html
? ~
以上数据来源于网络,如有侵权,请联系删除。
上一篇:
面试官:你说使用过ZooKeeper,那来说说他的基本原理吧
下一篇:
看完这篇,再也不怕面试被问HashMap了~
评论
(0)
提交
类别
基础编程学习
HTML
PHP
Python
编程知识库
后端开发知识
热门文章
Java并发中的同步容器与并发容器,你了解多少?
Innodb中的事务隔离级别和锁的关系,难倒一半面试者!
SpringBoot + minio实现分片上传、秒传、续传
面试官:你知道消息队列如何保证数据不丢失吗?
JAVA知识 Java8新特性
面试官:谈谈为什么要限流,有哪些限流方案?
说说动态代理与静态代理区别
面试官:思考Tomcat 类加载器为什么要违背双亲委派模型?
boot-admin 基于SpringBoot的后台权限管理系统,可作为脚手架,用于快速搭建项目
SpringBoot+Vue+App+硬件实现智能家居系统项目