ACE中的Proactor和Reactor
ACE_Select_Reactor是除Windows之外所有平台使用的默认反应器实现,在这些系统上最终会用select()系统函数进行等待。在Windows上ACE_WFMO_Reactor是默认的反应器实现。该实现没有使用select()多路分离器,而是使用了WaitForMultipleObjects()。在使用ACE_WFMO_Reactor时,需要一些权衡:ACE_WFMO_Reactor只能登记62个句柄。底层的WaitForMutipleObejcts()函数是64个,ACE在内部使用了其中的两个;I/O类型,ACE_WFMO_Reactor只在socket句柄上支持handler_input()、handler_output()和handler_exception()的I/O回调;只要句柄能用于WaitForMultipleObjects(),ACE_WFMO_Reactor就能对其做出反应,比如文件变更通知句柄和事件句柄。
ACE_WIN32_Proactor是Windows上的ACE_Proactor实现,它使用I/O完成多口进行完成事件检测。在初始化异步操作工厂时(比如ACE_Asynch_Read_Stream或ACE_Aysnch_Write_Stream),I/O句柄会与Proactor的I/O完成端口关联在一起。在这种实现里,Windows的GetQueuedCompletionStatus()函数用于执行时间循环。多个线程可以同时执行ACE_WIN32_Proactor时间循环。POSIX系统上的ACEProactor实现提供了多种用于发起I/O操作、并检查其完成的机制。ACE所封装的POSIX异步I/O机制支持read()和write()操作,但不支持与TCP/IP连接相关的操作,为了支持ACE_Asynch_Acceptro和ACE_Asynch_Connector的函数,ACE使用了一个单独的线程来执行与连接有关的操作。因此,在POSIX平台上使用Proactor框架时,程序将会运行多个线程。ACE的内部机制使得你不用再多个线程中对时间进行处理,所以你无需增加任何特殊的加锁或同步。
首先想写网络处理程序,那么要清楚各个步骤的限制是什么,简单的说,肯定有读取(写)数据,和处理数据两个部分,那么这个两个部分有什么特点呢?读取数据主要是I/O操作,而单个的I/O操作可能会有等待,而且I/O操作对cpu的耗费很少,最重要的是I/O速度与cpu速度比跟龟速差不多。一个线程就可以处理很多路的I/O操作,在一次I/O完成的时候会激活分离器,由分离器调用事件处理器。做个假设,可能同时有1000个数据请求,如果不用reactor,那么可能会开启1000个线程或者进程进行服务,这个时候首先线程之间的切换和锁开销是非常大的,而如果采用reactor,可能会只用一个读线程,轮流读取这1000个请求,然后当其中的一个读取完成后,激活分离器,分离器调用事件处理器,这样的话,如果系统处理数据很快的话,那么同时存在的线程可能很少,就减少了系统的线程创建切换和销毁。
记得以前写过的服务器程序,都是有一个读取请求线程池,一个处理线程池,这样的话其实和Reactor模式的原理差不多,把读取和处理分离开来,然后用一个消息队列来完成读取与处理的通信,这样会用到锁,锁的设计不仅复杂而且会有系统开销。但是Reactor没有用锁,只把一个buffer告诉分发器,当读取结束就回调处理handler。(在ACE中是否用锁实现的不是很确定)而且,想刚才那样设计交互就比较麻烦,如果光是读还比较简单,但是读到如果会送那么就比较麻烦了。
下面是别的博文上总结Reactor和Proactor的读的过程:
在Reactor中实现读:- 注册读就绪事件和相应的事件处理器
- 事件分离器等待事件
- 事件到来,激活分离器,分离器调用事件对应的处理器。
- 事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
在Proactor中实现读:- 处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这种情况下,处理器无视IO就绪事件,它关注的是完成事件。
- 事件分离器等待操作完成事件
- 在分离器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分离器读操作完成。
- 事件分离器呼唤处理器。
- 事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分离器
简单总结下相同点与不同点:
相同点
1)都有Event Demutiplexer(为什么叫事件多路分离器呢?主要是从它的功能上看,它能够把事件源的I/O时间分离出来,并分发到对应的read/write事件处理区(Event Handler)),负责回调handler。
2)读(写)数据,与读结束后处理为分开的两个过程,这也是Reactor的重点所在,不用一个线程完成从读数据一直到处理结束,读数据线程和处理线程是两个不同的线程。这样做有个优点,因为读取数据可能会很耗费时间,读取其实瓶颈为系统的I/O,一个线程就可以处理很多路数据的I/O处理。
不同点:
1)Proactor是在I/O操作完成才回调handler,而Reactor是在I/O可以进行读或写操作时候调用handler。
2)Proactor的读写是利用操作系统支持异步I/O读写操作完成的,而Reactor的I/O操作是由用户完成的。
3)Reactor为同步的,需要用户自己去执行I/O操作然后等待I/O操作完成,在执行某些操作,Proactor为异步的,发起I/O操作后就交给操作系统了,只关心IO完成事件。这里一开始理解有个小的误区,
其实Reactor和Proactor在等待I/O事件到来的时候都可以理解为异步的,可能都用到select()底层函数,当端口可操作时候由操作系统异步的通知,这个时候Reactor收到通知,调用handler。然而Proactor收到端口可操作的时候还不满意,需要在I/O操作完成后再异步的通知,这样Proactor相当于两层异步操作,而说Reactor和Proactor的“异步”,指的为后面的I/O操作。
“Reactor框架中用户定义的操作是在实际操作之前调用的。比如你定义了操作是要向一个SOCKET写数据,那么当该SOCKET可以接收数据的时候,你的操作就会被调用;而Proactor框架中用户定义的操作是在实际操作之后调用的。比如你定义了一个操作要显示从SOCKET中读入的数据,那么当读操作完成以后,你的操作才会被调用。”