对比高性能I/O设计模式-Reactor/Proactor

通常,I/O复用机制都需要事件分享器。分享器对象可将来自事件源的I/O事件分离出来,并分发到对应的Read/Write事件处理器。开发人员预先注册需要处理的
事件及该事件对应的事件处理器。
Reactor和Proactor都涉及到了事件分享器,不同的是,Reactor是基于同步I/O的,而Proactor是与异步I/O相关。

在Reactor模式中,事件分离器等待某个事件或者某个操作的状态发生,比如文件描述符可读写或是socket可读写,事件分离器就将这个时间传给事先注册的事件处
理器(事件处理函数或者回调函数),由后者来做实际的读写操作。

而在Proactor模式中,事件处理器直接发起一个异步读写操作,发起时,需要提供用于存放读到数据的缓存区、读的数据大小。以及这个请求完成后的回调函数等信息。
事件分离器收到请求后,默默等待这个请求的完成,然后转发完成事件给对应的事件处理器。这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可以称之为“
系统级别”的或者“真正意义上”的异步,因为具体的读写操作是由操作系统来代劳的。

我们用读操作来举例,更好的理解Reactor和Proactor两种模式的区别。
Reactor:

  • 某个事件处理器宣称它对某个socket上的读事件很感兴趣(发起读请求)
  • 事件分离器等待这个事件的发生
  • 事件发生时,事件分离器被唤醒,然后通知先前那个事件处理者
  • 事件处理者接到通知,于是去那个socket上读数据,如果需要,它再次宣称对这个socket上的读事件感兴趣,一直重复上面的步骤

Proactor(系统级别异步):

  • 事件处理者直接发起一个写请求,这个时候,事件处理者不关心读事件,它只是发请求而已,发完之后就不管具体的事了,,只等着系统帮他搞定之后给他的回话。
  • 事件分离者等待着这个读事件的完成,与此同时,系统已经在一边开始干活了,它从目标socket读取数据,放入用户提供的缓存区中,完成后,通知事件分离者
  • 事件分离者通知事件处理者,读事件已经完成
  • 事件处理者会从它所提供的缓存区中获得它想要读的数据,按照自己我需要进行处理。如果需要,它可以像之前一样继续发起一个操作请求,无论是写操作还是读操作,和上面的几个步骤一样

然而,并不是所有的操作系统都为底层异步提供健壮的支持,比如许多Unix系统。

正如上面所提到过的,真正的异步模式需要操作系统级别的支持。由于事件处理者及操作系统交互的差异,为Reactor何Proactor设计一种通用统一的外部接口是
非常困难的。因此,大多网络库或是开发框架都是在两种模式之一,唯一一个包含两者的ACE,也是为Window准备了ACE
Proactor、为Unix系列提供了ACE Reactor,并且两者的代码分别独立维护。

为了解决这个情况,我们可以将Reactor稍做调整,模拟成异步的Proactor模型(主要是在事件分离器里完成本该事件处理者做的实际读写工作,我们称这种方法
为“模拟异步”)。

Proactor(模拟异步):

  • 事件处理者宣告对读事件感兴趣,并提供用于存储读出的结果的缓存区、读的数据长度等参数
  • 事件分离器等待,当事件到来时,分离器被唤醒,并去执行非阻塞的读操作,读取完毕后,通知事件处理者。
  • 事件处理者这时会被通知读操作完成,并且已经获得了想要获取的数据。

我们看到,通过为分离者添加一些功能,就可以让Reactor模式转换为Proactor模式。所有这些被执行的操作,其实是和Reactor模式应用时完全一致的。
我们只是把工作打散分配给不同的角色去完成而已。这样并不会有额外的开销,也不会有性能的损失。
我们再来对比一下Reactor和模拟的Proactor两个过程。

Reactor:

  • 等待事件
  • 发“已经可读”事件给实现注册的事件处理者或者回调
  • 读数据
  • 处理数据

Proactor:

  • 等待事件
  • 读数据
  • 发“数据已经读取完毕”事件给实现注册的事件处理器或者回调
  • 处理数据

在没有底层异步I/O
API支持的操作系统,这种方法可以帮我们隐藏掉socket接口的差异,提供一个完全可用并且统一的“异步接口”。这样我们就可以开发真正平台独立的通用接口了。

参考博客:
http://blog.jobbole.com/59676/
https://www.zhihu.com/question/26943938/answer/35034068

请赐予我钱进的动力吧~
0%