关于Reactor和Proactor的区别

/*********************************************************************
 * Author  : Samson
 * Date    : 07/12/2015
 * Test platform:
 *              gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
 *              GNU bash, 4.3.11(1)-release (x86_64-pc-linux-gnu) 
 * Nginx version:
 *              Nginx 1.6.2
 *              Nginx 1.8.0
 * *******************************************************************/

系统I/O 可分为阻塞型, 非阻塞同步型以及非阻塞异步型。

阻塞型I/O意味着控制权只到调用操作结束了才会回到调用者手里。


非阻塞同步是会立即返回控制权给调用者的。调用者不需要等等,它从调用的函数获取两种结果:要么此次调用成功进行了;要么系统返回错误标识告诉调用者当前资源不可用,你再等等或者再试度看吧。比如read()操作, 如果当前socket无数据可读,则立即返回EWOULBLOCK/EAGAIN,告诉调用read()者”数据还没准备好,你稍后再试”。


非阻塞异步调用中,稍有不同。调用函数在立即返回时,还告诉调用者,这次请求已经开始了。系统会使用另外的资源或者线程来完成这次调用操作,并在完成的时候知会调用者(比如通过回调函数)。POSIX的aio_read()来说,调用它之后,函数立即返回,操作系统在后台同时开始读操作。即是将工作交给了内核去完成这个操作。


在以上三种IO形式中,非阻塞异步是性能最高、伸缩性最好的。


两种IO多路复用方案:Reactor and Proactor


一般情况下,I/O 复用机制需要事件分享器(event demultiplexor)。 事件分享器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊: 谁的什么东西送了, 快来拿吧。开发人员在开始的时候需要在分享器那里注册感兴趣的事件,并提供相应的处理者(event handlers),或者是回调函数; 事件分享器在适当的时候会将请求的事件分发给这些handler或者回调函数。


涉及到事件分享器的两种模式称为:Reactor and Proactor。 Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的。 在Reactor模式中,事件分离者等待某个事件或者可应用或个操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分离者就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。


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


举另外个例子来更好地理解Reactor与Proactor两种模式的区别。这里我们只关注read操作,因为write操作也是差不多的。下面是Reactor的做法:

某个事件处理者宣称它对某个socket上的读事件很感兴趣;

事件分离者等着这个事件的发生;

当事件发生了,事件分离器被唤醒,这负责通知先前那个事件处理者;

事件处理者收到消息,于是去那个socket上读数据了。 如果需要,它再次宣称对这个socket上的读事件感兴趣,一直重复上面的步骤;


下面再来看看真正意义的异步模式Proactor是如何做的:

事件处理者直接投递发一个写操作(当然,操作系统必须支持这个异步操作)。 这个时候,事件处理者根本不关心读事件,它只管发这么个请求,它魂牵梦萦的是这个写操作的完成事件。这个处理者很拽,发个命令就不管具体的事情了,只等着别人(系统)帮他搞定的时候给他回个话。

事件分离者等着这个读事件的完成(比较下与Reactor的不同);

当事件分离者默默等待完成事情到来的同时,操作系统已经在一边开始干活了,它从目标读取数据,放入用户提供的缓存区中,最后通知事件分离者,这个事情我搞完了;

事件分享者通知之前的事件处理者: 你吩咐的事情搞定了;

事件处理者这时会发现想要读的数据已经乖乖地放在他提供的缓存区中,想怎么处理都行了。如果有需要,事件处理者还像之前一样发起另外一个写操作,和上面的几个步骤一样。


标准的经典的 Reactor模式:


步骤 1) 等待事件 (Reactor 的工作)

步骤 2) 发”已经可读”事件发给事先注册的事件处理者或者回调 ( Reactor 要做的)

步骤 3) 读数据 (用户代码要做的)

步骤 4) 处理数据 (用户代码要做的)


模拟的Proactor模式:


步骤 1) 等待事件 (Proactor 的工作)

步骤 2) 读数据(看,这里变成成了让 Proactor 做这个事情)

步骤 3) 把数据已经准备好的消息给用户处理函数,即事件处理者(Proactor 要做的)

步骤 4) 处理数据 (用户代码要做的)

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


那么,综上所述,这两者的区别是什么呢?

简单直观的理解:

1、Reactor模式是等待关心的动作的发生后,将如何处理这个动作的后续交给了用户态的应用本身来处理,Reactor的事件分享器只关心事件的发生,其它的就完全交给应用程序来处理了;而Proactor模式则是只关心由操作系统(内核)完成异步非阻塞的操作后返回的结果;

2、Proactor场景中只能够使用异步非阻塞的syscall(系统调用),而Reactor的场景中更多地是使用非阻塞同步的syscall(系统调用);


对于不明白

IO - 同步,异步,阻塞,非阻塞概念的请先看下这个科普:

http://blog.csdn.net/historyasamirror/article/details/5778378


REF:

http://www.artima.com/articles/io_design_patterns.html


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值