Reactor 与 Proactor

Reactor 与 Proactor 比较

一.概述

系统I/O可以被阻断,或非阻塞同步,或异步非阻塞。阻塞I/O意味着在操作完成之前,系统不会将控制权返回给调用者,导致调用者被阻塞,并且在此期间无法执行任何其他任务。最重要的是,在等待I/O完成时,调用程序线程不能重用于其他请求处理,因此在此期间形成了资源的浪费。例如:在套接字中调用read()时如果缓冲区为空,则在阻塞模式下对套接字的操作将不会返回控制,直到有数据可用为止。相反,非阻塞同步调用会立即将控制权返回给调用者。调用者不会等待,并且被调用的系统立即返回两个响应之一:如果执行了调用并且结果准备就绪,则告知调用者。或者,被调用的系统可以告诉调用者系统没有资源(套接字中没有数据)来执行请求的操作。在这种情况下,调用者可以重复发起调用,直到成功为止。例如,在套接字中调用read()时如果缓冲区为空时可能会返回-1,并将errno设置为EWOULBLOCK/EAGAIN,意味着“未就绪,请稍后再试”。在非阻塞异步调用中,调用函数立即将控制权返回给调用者,报告所请求的操作已启动。调用系统将使用其他系统资源/线程执行调用者的请求,并在结果准备好处理时通知调用者(例如,通过回调)。例如,Windows ReadFile() 或POSIX aio_read()API立即返回并启动内部系统读取操作。在这三种方法中,这种非阻塞异步方法提供了最佳的可扩展性和性能。本文研究了不同的非阻塞I/O复用机制,并提出了单一的多平台设计模式/解决方案。

二. Reactor和Proactor

通常,I/O复用机制依赖于一个事件多路分解器,即调度从有限数目的源到相应的读/写事件处理程序的I/O事件的对象。开发人员注册事件,并提供事件处理程序或回调,将所请求的事件传递给事件处理程序。涉及事件多路分解器的两种模式称为Reactor和Proactor。Reactor模式涉及同步I/O,而Proactor模式涉及异步I/O. 在Reactor中,事件等待指示文件描述符或套接字何时准备好进行读取或写入操作的事件。解复用器将此事件传递给适当的处理程序,该处理程序负责执行实际的读取或写入。
相反,在Proactor模式中,代表处理程序的处理程序或事件多路分解器启动异步读写操作。I/O操作本身由操作系统(OS)执行。传递给OS的参数包括用户定义的数据缓冲区的地址,OS从中获取要写入的数据,或者操作系统将数据读取到的地址。事件解复用器等待指示I/O操作完成的事件,并将这些事件转发给适当的处理程序。例如,在Windows上,处理程序可以启动异步I/O(在Microsoft术语中重叠)操作,并且事件解复用器可以等待IOCompletion事件。这种经典异步模式的实现基于异步操作系统级API,我们将此实现称为“系统级”异步,因为应用程序完全依赖操作系统来执行实际I/O.一个例子将帮助我们理解Reactor和Proactor之间的区别。我们将重点关注这里的读操作,因为写实现类似。这是Reactor的读物:

事件处理程序声明对I / O事件感兴趣,这些事件指示在特定套接字上读取的准备情况
事件解复用器等待事件
一个事件进入并唤醒解复用器,解复用器调用适当的处理程序
事件处理程序执行实际的读取操作,处理读取的数据,声明对I / O事件的重新关注,并将控制权返回给调度程序
相比之下,这是Proactor中的读操作(真正的异步):

处理程序启动异步读取操作(注意:操作系统必须支持异步I / O)。在这种情况下,处理程序不关心I / O就绪事件,而是注册接收完成事件的兴趣。
事件解复用器等待操作完成
当事件多路分解器等待时,OS在并行内核线程中执行读操作,将数据放入用户定义的缓冲区,并通知事件多路分解器读取完成
事件解复用器调用适当的处理程序;
事件处理程序处理来自用户定义缓冲区的数据,启动新的异步操作,并将控制返回给事件多路分解器。
目前的做法
开源C ++开发框架ACE [ 1,3 ]由Douglas施密特开发。,等人,提供了一个宽范围的独立于平台的,低级并发支持类(线程,互斥,等)。在顶层,它提供了两组独立的类:ACE Reactor和ACE Proactor的实现。虽然它们都基于与平台无关的原语,但这些工具提供了不同的接口。

的ACE摄给出在MS-Windows更好的性能和鲁棒性,作为Windows提供了一个非常有效的异步API,基于操作系统级支持[ 4,5 ]。

遗憾的是,并非所有操作系统都提供完全强大的异步操作系统级支持。例如,许多Unix系统没有。因此,ACE Reactor是UNIX中的首选解决方案(目前UNIX没有强大的套接字异步功能)。因此,为了在每个系统上实现最佳性能,联网应用程序的开发人员需要维护两个独立的代码库:基于ACE Proactor的Windows解决方案和基于ACE Reactor的基于Unix的系统解决方案。

正如我们所提到的,真正的异步Proactor模式需要操作系统级支持。由于事件处理程序和操作系统交互的不同性质,很难为Reactor和Proactor模式创建通用的统一外部接口。反过来,这使得创建完全可移植的开发框架并封装接口和OS相关的差异变得很困难。

提出的解决方案
在本节中,我们将提出解决设计Proactor和Reactor I / O模式的可移植框架的挑战。为了演示此解决方案,我们将通过从解复用器内部的事件处理程序移动读/写操作,将Reactor解复用器I / O解决方案转换为模拟的异步I / O(这是“模拟异步”方法)。以下示例说明了读取操作的转换:

事件处理程序声明对I / O事件(读取准备)的兴趣,并为解复用器提供诸如数据缓冲区的地址或要读取的字节数之类的信息。
Dispatcher等待事件(例如,打开select());
当一个事件到来时,它唤醒了调度员。调度程序执行非阻塞读取操作(它具有执行此操作所需的所有信息),并在完成时调用适当的处理程序。
事件处理程序处理来自用户定义缓冲区的数据,声明了新兴趣,以及有关数据缓冲区放置位置的信息以及I / O事件中要读取的字节数。然后,事件处理程序将控制权返回给调度程序。
我们可以看到,通过向解复用器I / O模式添加功能,我们能够将Reactor模式转换为Proactor模式。就工作量而言,这种方法与Reactor模式完全相同。我们只是在不同的角色之间转移责任 没有性能下降,因为执行的工作量仍然相同。这项工作只是由不同的演员执行。以下步骤列表表明每种方法执行的工作量相等:

标准/经典反应器:

步骤1)等待事件(反应堆工作)
步骤2)将“Ready-to-Read”事件发送给用户处理程序(Reactor job)
步骤3)读取数据(用户处理程序作业)
步骤4)处理数据(用户处理程序作业)
拟议模拟的前提:

步骤1)等待事件(前驱工作)
步骤2)读取数据(现在是Proactor作业)
步骤3)将“Read-Completed”事件分派给用户处理程序(Proactor job)
步骤4)处理数据(用户处理程序作业)
对于不提供异步I / O API的操作系统,此方法允许我们隐藏可用套接字API的反应性质并公开完全主动的异步接口。这使我们能够创建一个完全可移植的平台无关解决方案,并具有通用的外部接口。

你可能感兴趣的:(java)