美文网首页
IO多路复用

IO多路复用

作者: nnnnxcj | 来源:发表于2019-10-19 15:04 被阅读0次

相关概念:并行、并发、同步、异步、阻塞、非阻塞

  • 并发:是指在一个时间段内,有多个程序同时在CPU上运行,但是任意时刻只有一个程序在CPU上运行;
  • 并行:是指在任意时刻点上,有多个程序同时运行在CPU上;
  • 同步:是指程序代码调用IO操作的时候, 需要等到IO操作完成之后才返回的运行方式;
  • 异步:是指程序代码调用IO操作的时候,不需要等到IO操作完成之后才返回的运行方式;
  • 阻塞:是指调用函数时候当前线程/进程被挂起;
  • 非阻塞: 是指调用函数时候当前线程/进程不会被挂起,而是立即返回

IO模型

对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的缓冲区,最后交给进程。当一个read操作发生时,它会经历两个阶段:

  1. 等待数据准备
  2. 将数据从内核拷贝到进程中
    根据这俩阶段是否阻塞,Linux产生以下4种模型:
  • 同步阻塞IO(Blocking IO)
    上面两个阶段都发生阻塞;调用阻塞IO会一直block住进程,直到操作完成。
  • 同步非阻塞IO(Non-blocking IO)
    在第1阶段没有阻塞,在第2阶段发生阻塞;调用非阻塞IO会在kernel准备数据的情况下立即返回;非阻塞IO需要不断轮询,查看数据是否已经准备好了。
  • 多路复用IO(IO Multiplexing)
    I/O多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
  • 异步IO(Asynchronous IO)
    异步IO操作不会导致请求的进程被blocked。当发出IO操作请求,直接返回,等待IO操作完成后,再通知调用进程。

IO多路复用 select, poll, epoll

select/poll/epoll都是采用I/O多路复用机制的,其中select/poll是采用无差别轮询方式,而epoll是采用最小的轮询方式。

忙轮询

非阻塞的I/O需要轮询查看流是否已经准备好了,比较典型的方式是忙轮询。忙轮询方式是通过不停的把所有的流从头到尾轮询一遍,查询是否有流已经准备就绪,然后又从头开始。如果所有流都没有准备就绪,那么只会白白浪费CPU时间。轮询过程可以参照如下:

while true {
    for i in stream[] {
        if i has data
              read until unavailable
    }
}
无差别轮询

为了避免白白浪费CPU时间,我们采用另外一种轮询方式,无差别的轮询方式。即通过引进一个代理,这个代理为select/poll,这个代理可以同时观察多个流的I/O事件。当所有的流都没有准备就绪时,会把当前线程阻塞掉;当有一个或多个流的I/O事件就绪时,就从阻塞状态中醒来,然后轮询一遍所有的流,处理已经准备好的I/O事件。轮询的过程可以参照如下:

while true {
    select(streams[])
    for i in streams[] {
        if i has data
              read until unavailable
    }
}

如果I/O事件没准备就绪,那么我们的程序就会阻塞在select处。我们通过select那里只是知道了有I/O事件准备好了,但不知道具体是哪几个流(可能有一个,也可能有多个),所以需要无差别的轮询所有的流,找出已经准备就绪的流。可以看到,使用select时,我们需要O(n)的时间复杂度来处理流,处理的流越多,消耗的时间也就越多。

最小轮询

无差别的轮询方式有一个缺点就是,随着监控的流越来越多,需要轮询的时间也会随之增加,效率也会随之降低。所以还有另外一种轮询方式,最小轮询方式,即通过epoll方式来观察多个流,epoll只会把发生了I/O事件的流通知我们,我们对这些流的操作都是有意义的,时间复杂度降低到O(k),其中k为产生I/O事件的流个数。轮询的过程如下:

while true {
    active_stream[] = epoll_wait(epollfd)
    for i in active_stream[] {
        read or write till unavailable
    }
}

select

系统提供select函数来实现多路复用输入/输出模型,select系统调用是用来让我们的程序监视多个文件句柄的状态变化。程序会阻塞在select函数上,直到被监视的文件句柄中有一个或多个发生了状态变化。

poll

poll的处理机制与select类似,只是poll选择了pollfd结构体来处理文件描述符的相关操作。poll函数与select函数的最大不同之处在于:select函数有最大文件描述符的限制,一般1024个,而poll函数对文件描述符的数量没有限制。但select和poll函数都是通过轮询的方式来查询某个文件描述符状态是否发生了变化,并且需要将整个文件描述符集合在用户空间和内核空间之间来回拷贝,这样随着文件描述符的数量增加,相应的开销也随之增加。

epoll

epoll是在Linux内核2.6引进的,是select和poll函数的增强版。与select相比,epoll没有文件描述符数量的限制。epoll使用一个文件描述符管理多个文件描述符,将用户关心的文件描述符事件存放到内核的一个事件列表中,这样在用户空间和内核空间只需拷贝一次。
epoll两种工作模式:
水平触发LT(level trigger)和边沿触发ET(edge trigger)。默认的情况下为LT模式。
LT模式与ET模式的区别在于:
LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

综上,IO复用机制可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立即通知相应程序进行读或写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。

相关文章

网友评论

      本文标题:IO多路复用

      本文链接:https://www.haomeiwen.com/subject/usbimctx.html