Reactor And Proactor

2017/03/05

在高性能I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作。

先明白几个概念,同步 VS 异步, 阻塞 VS 非阻塞。

同步和异步是针对应用程序和内核的交互而言的。

同步:用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪。 异步:用户进程触发IO操作以后,便开始做其他事情,而当IO操作已经完成的时候,用户进程会得到IO完成的通知。

阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式。说白了是一种读取或者写入操作函数的实现方式;

阻塞:读取或者写入函数将一直等待 非阻塞:读取或写入函数会立即返回一个状态值

同步: 阻塞和非阻塞

同步阻塞IO

在此种方式下,用户进程在发起一个IO操作,必须等待IO操作的完成,只有当真正完成IO操作以后,用户进程才能继续运行。Java传统IO模型属于这一种。

同步非阻塞IO

为避免理解偏差,请同时参考此链接

在此种方式下,用户进程发起一个IO操作以后后边可返回做其它事情,但是用户进程需要时不时询问IO操作是否就绪,这就要求用户进程不停去询问,从而进入不必要的CPU资源浪费。其中目前Java的NIO就属于同步非阻塞IO。

异步: 阻塞和非阻塞

异步阻塞IO

此种方式下,指应用发起一个IO操作,不等待内核操作的完成,可以去执行其他内容。等内核完成IO操作以后会通知应用程序。

这其实就是同步和异步最关键的区别,同步必须等待或者主动地询问IO是否完成。底层是通过select系统调用来完成的,而select本身是阻塞的,采用select函数有个好处就是它可以同时监听多个文件描述符,从而提高系统并发性。

异步非阻塞IO

此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前Java还没有支持此种模型。

Reactor 和 Proactor

基于这些基本概念,在来看看Reactor模式和Proactor模式。

Reactor模式

以读取操作为例,

具体步骤:

  1. 应用程序注册读就绪事件,和相关联的事件处理器
  2. 事件分离器等待事件的发生
  3. 当发生就绪事件时,事件分离器调用第1步注册的事件处理器
  4. 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理

Proactor模式

以读取操作为例,

具体步骤:

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

Proactor的优势

  1. Proactor模式消除了Fork和上下文切换的开销;

Proactor的劣势

与其他设计模式类似:

  1. 应用复杂度增加了. 与线性编程模型不同, 这里要将不同操作的回调函数写到不同的连接处理函数中.
  2. 异步事件难于调试. 因为所有操作是异步的, 难以追踪程序的执行顺序.

总结

  1. 同步和异步是相对于应用和内核的交互方式而言的,同步需要等待或者主动询问,而异步则是内核在IO事件发生的时候通知应用程序;
  2. 阻塞和非阻塞仅仅是系统在call系统调用时候的函数实现方式而已;

参考

Reactor Sequence Diagram http://www.slideshare.net/ConversionMeetup/dejan-pekter-nordeus-reactor-design-pattern