前言
先大概了解一下这方面的知识
-
IO多路复用指的是单个进程或者线程能同时监听处理多个IO请求,
-
select、epoll、poll是LinuxAPI提供的复用方式。本质上是由
操作系统内核缓存fd文件描述符
, 使得单个进程线程能监视多个文件描述符。 -
select是将所有文件描述符的集合从用户空间拷贝到内核空间,
底层是数组
。 -
poll和select相似,主要区别是底层采用
链表
,从而使得监听文件描述符个数不再受限, 但是还是需要多次内核与用户的复制
,并且用户空间需要通过轮询O(n)
才能确定哪些文件描述符上发生了事件。 -
epoll底层采用
红黑树
,在内核空间创建需要关注的文件描述符的红黑树,内核监听时会将发生事件的描述符加入队列中,返回到用户空间的时候只需要返回队列中的数据
即可。 -
epoll通过这种方式使得
减少了每次用户到内核的复制过程
,同时用户空间通过O(1)
复杂度就可以知道哪些文件描述符发生了事件。
回答
传统的BIO,NIO,AIO有一个缺点就是每执行一次io事件就会执行一次系统调用
,系统调用需要从用户态陷入到内核态中,非常影响性能。
而IO多路复用可以降低系统调用的次数从而达到提升效率的效果。IO多路复用提供三种实现方式分别是select,poll和epoll。
select
1.在select中,会通过轮循的方式去执行select,若当前没有就绪事件即select函数返回值为0,那么便会阻塞直到有就绪事件进入结束阻塞然后将数据从内核态拷贝到用户态再去处理数据。处理完毕后开始下一次的select轮循。select函数包括如下参数:
- nfds:当前监听的事件描述符的最大值加一,因为select只会返回有事件就绪不会告诉你那些事件就绪,所以需要去遍历并判断,加入该参数就可以缩小遍历范围提高效率。
这三个参数既可以用于调用函数时作为需要监听的判断集合(集合本身是一个数组,长度为1024,所以select类型有
监听事件的数量限制,数组内容种0表示不需要监听,1表示需要监听
),同时也可以作为select返回后的就绪事件的
集合(0表示未就绪,1表示就绪),因为其双重功能的原因,所以在每次系统调用时都需要重置这三个集合。
-
timeout:0表示执行超时,-1表示出现异常。
select函数的返回值为当前就绪事件的个数
。 -
poll方式的执行效果和select类型,它通过以链表来记录事件的方式解决了每次都需要重设监听描述符集合的问题和监听描述符数量有限的问题。
poll
poll函数参数列表:
-
用来记录监听描述符和就绪描述符的链表。链表的每个节点记录了当前监听节点的描述符的值和它是否已经就绪判断。
-
nfds
:需要监听的描述符个数,和select类似 -
timeout,超时时间,和select类似。返回值及其意义和select函数相同。但是poll依然存在着每次系统调用都需要传入fd和不知道具体那些描述符就绪需要遍历的问题。
epoll系统调用包含三个方法
epoll_create(epoll对象创建)
需要传入一个监听描述符个数的参数size。生成一个epoll对象并将其放入到内核态中并返回该epoll的描述符,在内核态的该epoll对象有三个属性分别是:
epoll_crtl(事件注册)
将具体的事件通过该方法注册到epoll对象中并再内核态生成一个epitem来关联epoll对象,同时epitem还会关联一个回调函数,当该事件就绪后通过epoll_wait()进入回调函数将数据从内核态赋值到用户态并处理。
epoll_wait()
会先去判断epoll对象的就绪列表中是否有内容。若没有则阻塞,只有事件就绪。若有则取调用相应的回调函数处理数据。epoll通过注册和更新红黑树的形式来存储fds使得其不用每次系统调用是都传入fds。通过就绪队列的方式实现了不用一一判断就绪事件
epoll缺点是
1,只支持在linux中使用,移植性较差。
2,对于请求较少的情况效果反而没有select好
补充
当进行一次IO访问,数据会会先被拷贝到操作系统内核的缓冲区
,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间
,所以,当一个read操作发生时,会经历两个阶段,先等待数据准备就绪,然后将数据从内核拷贝到进程中
,
由于这两个阶段,Linux系统产生了下面五种网络模式的方案:
select、poll、epoll本质上也是同步IO
,因为他们都需要在读写事件就绪后自己负责读写,也就是这个过程是阻塞的,
与多进程和多线程技术相比,IO多路复用的最大优势是系统开销小,系统不必创建进程或线程
,从而大大减少了系统的开销,能力更多的连接。 其中IO多路复用的本质就是通过系统内核缓冲IO数据
,让单个进程可以监视多个文件描述符(FD),一旦某个描述符读就绪或者写就绪,可以通知程序进行相应的读写操作,也就是使用单个进程同时处理多个网络连接IO,
它的原理就是select、poll、epoll不断轮询所负责的socket
,当某个socket有数据达到了,就通知用户进程,select和poll的时间复杂度都是O(n)
,epoll的时间复杂度是O(1)
。
Epoll是Linux目前大规模网络并发程序开发的首选
,在绝大多数情况下性能远超select和poll,但是在并发连接不高的情况下,多线程+阻塞IO的方式可能会更好
。