note:本文以及《说说Redis的非阻塞IO多路复用技术》都是NIO学习的预热,本文对常见的四种IO模型进行归纳总结。

常见的IO模型有:

  1. 同步阻塞IO(Blocking IO):即传统的IO模型
  2. 同步非阻塞IO(Non-blocking IO)
  3. IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO
  4. 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO

1 同步阻塞IO

简单,就不多说了。
同步阻塞IO

2 同步非阻塞IO

不难,也不多说。

这里写图片描述

3 IO多路复用

这里写图片描述

用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。

从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

虽然上述方式允许单线程内处理多个IO请求,但是每个用户请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。可以使用了Reactor设计模式实现。
这里写图片描述

Reactor:当用户将自己感兴趣的IO注册到Reactor,并提供相应的事件处理器。Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程执行相应的事件处理器进行数据读取、处理的工作。

下面是一张以服务员和顾客为例子的图:

这里写图片描述

4 异步IO

这里写图片描述

流程:

  1. 用户线程,注册IO和相应的事件处理器,此时事件处理器不关注具体的IO操作,而是关注IO操作是否完成,这是区别于Reactor的关键。
  2. 事件分离器等待IO操作完成
  3. 在事件分离器等待IO操作完成的时候,操作系统调用内核线程完成读取操作,并将IO操作的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。
  4. 事件分离器捕获到读取完成事件后,激活用户注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的IO操作。