代码改变世界

最简洁易懂的方式介绍I/O模型

2019-08-25 16:35  码年  阅读(483)  评论(0编辑  收藏  举报

什么是I/O模型?

简单来说IO模型是指程序以何种方式等待数据或者状态的就绪,进而针对数据或者状态进行处理。“就绪”指的是数据准备好了或者某个状态发生了。比如程序等待socket接收数据到buffer中以后再进行数据处理;再比如server端socket变成连接状态后进行某种处理。程序为什么要等待呢?因为程序不能确定数据的读写会在什么时候完成,状态会在什么时候改变,程序需要等待这些事件的发生,然后进行相应的处理。比如server端socket接收客户端发过来的数据,然后进行数据处理,但是程序并不知道什么时候数据才会发过来,只能等在那里。

有哪些常用模型?
常用I/O模型有阻塞模型,非阻塞模型,多路复用模型和异步模型。

阻塞模型 (Blocking I/O)
阻塞指的是I/O方法调用(比如read)在数据或者状态还没有就绪的时候,一直等待,直到就绪才返回。比如阻塞模式下的Socket的read方法,如果没有接收到数据,read方法将会阻塞,程序就会停在那里,不能做其他的事情。这种模型下,如果服务器想处理多个连接,那么就要为每个Socket连接创建一个单独的线程,开销会很大。

非阻塞模型 (Non-Blocking I/O)
非阻塞模型是I/O方法调用(比如read)无论数据有没有就绪,会马上返回。如果有数据就会读到数据,如果没有数据就返回一个错误码。应用程序需要用轮询的方式不断去检测数据有没有就绪,但是程序不会被阻塞,除了轮询程序还可以做其他事情。但是这种轮询的方式会浪费CPU的时间,效率不够高。

多路复用模型 (I/O Multiplexing)
如果说数据就绪了系统能通知程序,而不是让应用程序轮询检测数据的就绪状态,岂不是就解决了非阻塞模型中的效率问题?是的,多路复用模型就是利用了系统的通知功能。常用的I/O多路复用有select和epoll。

先说多路复用中的selector。selector可以同时监听多个fd(File Descriptor - 文件描述符,比如文件系统的文件,socket都可以看做是文件,可以用fd表示)的某个就绪事件。以socket为例,selector可以监听多个socket的数据就绪状态。程序调用select方法,当所有监听的socket数据都没有就绪的时候,select会阻塞在这里,当被监听的socket中有数据就绪的了,select方法就返回了,应用程序需要遍历所有的socket去尝试读取数据。这种模型使得用户程序可以用一个专门的线程负责监听所有的socket的状态,避免了阻塞模型中为每个socket单独创建一个线程的情形。但是select的问题也很明显,就是系统不能通知到具体是哪一个socket的数据就绪了,程序需要遍历整个监听列表中所有的socket,会浪费CPU。并且Select监听的fd的个数也是有限制的,Linux上是1024。Java的NIO就使用了selector模型。

那对于上面的例子,系统可不可以通知程序具体是哪些socket的数据就绪了?Linux上的epoll可以做到。epoll提供了三个API,epoll_create, epoll_ctl, epoll_wait,分别可以创建epoll数据结构,注册监听fd的事件(比如注册监听socket的数据接收),和等待事件的发生。调用epoll_wait方法时,如果监听的事件没有发生,则会阻塞。继续以server端socket为例,应用程序按照类似selector的方式使用epoll,不同的是当某些socket数据就绪的时候,可以获取就绪的socket列表,而不需要遍历和检测所有的socket。epoll内部以一种非常高效的方式提供这个就绪的fd列表,并且没有监听数目的限制。这使得linux系统可以以更简单高效的方式支持更多的连接。

异步模型 (Asynchronous I/O)
异步模型是指用户给I/O方法调用(比如read)提供一个回调方法,I/O方法调用会立刻返回,等到数据就绪时回调方法会执行。这个看上去很好用,把所有的处理都推给了系统,用户只需要关心回调方法中的数据处理就行了,但是在高并发情况下,处理好系统I/O程序和用户程序之间的CPU竞争比较困难。

混淆的阻塞/非阻塞,同步/异步
关于I/O模型我们经常看到阻塞/非阻塞,同步/异步这几个词,可能会想阻塞不就是同步,非阻塞不就是异步吗?其实它们说的不是一回事儿。阻塞和非阻塞针对的是I/O方法调用时的行为。阻塞是指方法调用会一直等待直到数据就绪才返回;非阻塞是无论数据是否就绪方法调用都立刻返回,如果数据没有就绪则方法返回错误码,需要轮询检测数据就绪状态,在数据就绪时读出数据。同步异步针对的是I/O方法调用返回数据的方式。同步是指数据是调用的I/O方法返回的(在没有数据的时候返回错误码);异步指的是系统在数据就绪的时候把数据放到用户程序指定的用户缓冲区,然后通知程序说好了,或者直接调用用户提供的回调方法,数据作为参数。按照这个定义,上面提到的阻塞,非阻塞和多路复用模型都是同步的,只有异步模型是异步的。

 

作者公众号(码年)扫码关注: