1.什么是IO多路复用
    一句话解释:单线程或单进程同时检测若干个文件描述符是否可以执行IO操作的能力。
    2.解决什么问题
    多路复用I/O技术由操作系统提供支持,并提供给各种高级语言进行使用。它针对阻塞式同步I/O和非阻塞式同步I/O而言有很多优势,最直接的效果就是它绕过了I/O在操作系统层面的accept()方法的阻塞问题。
    说在前头
    应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标的输入、中断信号等等事件,再比如web服务器如nginx,需要同时处理来来自N个客户端的事件。 逻辑控制流在时间上的重叠叫做 > 并发 而CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系统内部的调度,来做到对CPU处理的时分复用。这样多个事件流就可以并发进行,不需要一个等待另一个太久,在用户看起来他们似乎就是并行在做一样。
    但凡事都是有成本的。线程/进程也一样,有这么几个方面:

    1. 1. 线程/进程创建成本
    2. 1. CPU切换不同线程/进程成本 [Context Switch](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Context_switch)
    3. 1. 多线程的资源竞争0

    有没有一种可以.在单线程/进程中处理多个事件。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。流的方法呢?一种答案就是IO多路复用。
    因此IO多路复用解决的本质问题是在用更少的资源完成更多的事。
    为了更全面的理解,先介绍下在Linux系统下所有IO模型。
    I/O模型
    目前Linux系统中提供了5种IO处理模型

      1. 阻塞IO
      1. 非阻塞IO
      1. IO多路复用
      1. 信号驱动IO
      1. 异步IO
    

    阻塞IO
    这是最常用的简单的IO模型。阻塞IO意味着当我们发起一次IO操作后一直等待成功或失败之后才返回,在这期间程序不能做其它的事情。阻塞IO操作只能对单个文件描述符进行操作,详见readwrite
    非阻塞IO
    我们在发起IO时,通过对文件描述符设置O_NONBLOCK flag来指定该文件描述符的IO操作为非阻塞。非阻塞IO通常发生在一个for循环当中,因为每次进行IO操作时要么IO操作成功,要么当IO操作会阻塞时返回错误EWOULDBLOCK/EAGAIN,然后再根据需要进行下一次的for循环操作,这种类似轮询的方式会浪费很多不必要的CPU资源,是一种糟糕的设计。和阻塞IO一样,非阻塞IO也是通过调用read或writewrite来进行操作的,也只能对单个描述符进行操作。
    IO多路复用
    IO多路复用在Linux下包括了三种,selectpollepoll,抽象来看,他们功能是类似的,但具体细节各有不同:首先都会对一组文件描述符进行相关事件的注册,然后阻塞等待某些事件的发生或等待超时。更多细节详见下面的 “具体怎么用”。IO多路复用都可以关注多个文件描述符,但对于这三种机制而言,不同数量级文件描述符对性能的影响是不同的,下面会详细介绍。
    信号驱动IO
    信号驱动IO是利用信号机制,让内核告知应用程序文件描述符的相关事件。这里有一个信号驱动IO相关的例子
    但信号驱动IO在网络编程的时候通常很少用到,因为在网络环境中,和socket相关的读写事件太多了,比如下面的事件都会导致SIGIO信号的产生:

         1. TCP连接建立
         1. 一方断开TCP连接请求
         1. 断开TCP连接请求完成
         1. TCP连接半关闭
         1. 数据到达TCP socket
         1. 数据已经发送出去(如:写buffer有空余空间)
    

    上面所有的这些都会产生SIGIO信号,但我们没办法在SIGIO对应的信号处理函数中区分上述不同的事件,SIGIO只应该在IO事件单一情况下使用,比如说用来监听端口的socket,因为只有客户端发起新连接的时候才会产生SIGIO信号。
    异步IO
    异步IO和信号驱动IO差不多,但它比信号驱动IO可以多做一步:相比信号驱动IO需要在程序中完成数据从用户态到内核态(或反方向)的拷贝,异步IO可以把拷贝这一步也帮我们完成之后才通知应用程序。我们使用 aio_read 来读,aio_write 写。 同步IO vs 异步IO 1. 同步IO指的是程序会一直阻塞到IO操作如read、write完成 2. 异步IO指的是IO操作不会阻塞当前程序的继续执行所以根据这个定义,上面阻塞IO当然算是同步的IO,非阻塞IO也是同步IO,因为当文件操作符可用时我们还是需要阻塞的读或写,同理IO多路复用和信号驱动IO也是同步IO,只有异步IO是完全完成了数据的拷贝之后才通知程序进行处理,没有阻塞的数据读写过程。 3.目前有哪些IO多路复用的方案
    Linux: select、poll、epoll
    MacOS/FreeBSD: kqueue
    Windows/Solaris: IOCP
    常见软件的IO多路复用方案
    redis: Linux下 epoll(level-triggered),没有epoll用select
    nginx: Linux下 epoll(edge-triggered),没有epoll用select
    不同IO多路复用方案的优缺点
    poll vs select
    poll和select基本上是一样的,poll相比select好在如下几点:

         1. poll传参对用户更友好。比如不需要和select一样计算很多奇怪的参数比如nfds(值最大的文件描述符+1),再比如不需要分开三组传入参数。
         1. poll会比select性能稍好些,因为select是每个bit位都检测,假设有个值为1000的文件描述符,select会从第一位开始检测一直到第1000个bit位。但poll检测的是一个数组。
         1. select的时间参数在返回的时候各个系统的处理方式不统一,如果希望程序可移植性更好,需要每次调用select都初始化时间参数。
    

    而select比poll好在下面几点

         1. 支持select的系统更多,兼容更强大,有一些unix系统不支持poll
         1. select提供精度更高(到microsecond)的超时时间,而poll只提供到毫秒的精度。
    

    但总体而言 select和poll基本一致。
    epoll vs poll&select
    epoll优于select&poll在下面几点:

         1. 在需要同时监听的文件描述符数量增加时,select&poll是O(N)的复杂度,epoll是O(1),在N很小的情况下,差距不会特别大,但如果N很大的前提下,一次O(N)的循环可要比O(1)慢很多,所以高性能的网络服务器都会选择epoll进行IO多路复用。
         1. epoll内部用一个文件描述符挂载需要监听的文件描述符,这个epoll的文件描述符可以在多个线程/进程共享,所以epoll的使用场景要比select&poll要多。