IO复用的三种方法(select,poll,epoll)深入理解,包括三者区别,内部原理实现?

(一)IO复用是Linux中的IO模型之一,IO复用就是进程告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程处理,从而不会在单个IO上阻塞了,Linux中,提供了select、poll、epoll三种接口来实现IO复用
(二)select:
缺点:

1 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024。由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;
2 内核/用户空间内存拷贝,select需要大量的句柄数据结构,产生巨大开销;
3 select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生事件;
4 select的触发方式为水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么下次select调用还会将这些文件描述符通知进程;
(三)poll:

使用链表保存文件描述符,没有了文件描述符的限制,但其他的三个缺点依然存在
(四)epoll:
上面所说的select缺点都不存在,epoll使用了一个文件描述符管理了多个文件描述符。拿select模型为例,假设我们服务器需要支持100万个并发链接,则在_FD_SETSIZE为1024的情况下,则我们至少需要开辟1K个进程才能实现100万的并发连接,除了进程上下文切换的时间消耗,从内核到用户空间的拷贝,数据轮询,是系统难以承受的,因此,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。

epoll的设计与select完全不同,epoll通过在Linux内核申请一个简易的文件系统(文件一般使用什么数据结构实现?B+树),把原先的select/epoll调用分为3个部分:

1 调用epoll_create()建立了一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2 调用epoll_ctl()向epoll对象添加这100万个连接的套接字(ip地址+端口号)
3 调用epoll_wait()收集发生事件的连接

一、阻塞现象
什么是阻塞现象?
​ 当一个数据流中再也没有数据,read的时候,或者,我们的流中写满了数据在去write的时候,即无数据可读和无空间可写的尴尬现象,称为阻塞现象。

阻塞等待,非阻塞忙轮询

比如这个例子,送快递与收快递的例子

阻塞等待相当于快递员在送快递的过程中,客户可以空出大脑睡大觉(不占用CPU的宝贵时间片)
非阻塞忙轮询:指的是快递员在送快递的过程中客户每分钟催一次,占用了快递员的时间(占用了CPU,系统资源)

二、 阻塞死等待的缺点

​ 如果快递同一时刻到达,但是同一时刻只能签收并验货一份快递,因此在签收的时候,便借不到其他快递员的电话(多线程或者多进程)

三、解决阻塞死等待的方法

1、非阻塞、忙轮询

2. 方法2:select与poll

 select 代收员 比较懒,她只会告诉你快递到了,但是是谁到的,你需要挨个快递员问一遍。每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态;poll基本上也相同,只不过poll没有最大文件描述符限制,poll解决了select重复初始化的问题。但仍旧是轮寻排查
方法3:epoll

什么是epoll

  • 是IO多路转接技术
  • 只需关心活跃的连接,无需遍历全部的描述符集合
  • 能够处理大量的连接请求(系统可以打开的文件数目)