Proactor模式:一种用于异步事件分用和分发处理器的对象行为模式

Proactor模式
一种用于异步事件分用和分发处理器的对象行为模式
这篇论文出自第四期Pattern Languages of Programming会议年刊,在Allerton Park, Illinois,1997年九月。

概括
现代操作系统提供了多种模型用于开发并发应用。同步多线程是一种流行的模型用于开发那些同时执行多个操作的应用。然而,线程通常有很高的性能开销,而且需要掌握很深入的同步方面的知识。因此,越来越多的操作系统支持异步模型,它给并发带来便利的同时也大大减轻了多线程的开销和复杂性
proactor模式发表于一篇讲述如何高效地利用操作系统提供的异步模型去构建应用和系统的论文。当应用程序发起一个异步操作时,操作系统会替应用执行操作。这意味着应用程序可以让多个操作同时运行,却不需要有同等数量的线程。因此,proactor模式通过使用更少的线程和促使操作系统支持异步操作来简化并发编程和提升性能。

1 意图
proactor模式支持多个事件处理器的分用和分发,这种事件处理器会在异步事件完成的时候触发。这种模式通过整合完成事件的分用和它们的响应事件处理的分发来简化异步应用的开发。

2 动机
这节将讲述使用proactor模式的背景和原因。

2.1 背景和动力
proactor模式应该用于对执行并发操作有高性能要求,而且没有同步多线程和反应式编程的限制。为了阐明这些优点,考虑一种需要执行多个并发操作的网络应用。举个例子,一个高性能web服务器必须并发地处理来自多个客户端的http请求。图1展示了web浏览器和web浏览器之间常见的关系。当一个用户向浏览器发出命令去打开一个url,浏览器发送一个http get请求给web服务器。在收到请求以后,服务器解析、验证请求并发送特定的文件返回给浏览器。
开发高性能服务器有下面一些要求:
并发性——服务器必须能够同时执行多个客户端请求。
高效性——服务器必须最小化延迟,最大化吞吐量,并且避免不必要的cpu使用。
简化编程——服务器的设计应该简化使用高效的并发策略。
适用性——整合新的或者升级传输协议(比如说http1.1)的维护开销要尽量少。
一个web服务器可以使用多种并发策略实现,包括多同步线程、反应式同步事件分发(reactor)以及主动式异步事件分发(proactor)。接下来,我们会调研常用方法的缺点并且解释proactor模式是如何为高性能并发应用提供支持高效和灵活的异步事件分发策略的。

2.2 常用并发模型的常见的坑
同步多线程和反应式编程是实现并发的常见方式。这一节描述这些编程模型的缺点。

2.2.1 同步多线程并发
或许最直观的实现一个并发的web服务器的方式是使用同步多线程( synchronous multi-threading)。对于这种模型,多个服务器线程同时处理来自多个客户端的http get请求。 每个线程执行连接建立、http请求读取、请求解析、文件同步传输操作。因此,每个操作都会阻塞直到它完成。
同步线程的主要优势是简化应用代码。web服务器执行的客户端a的请求和客户端b的请求是独立的。因此,这很容易在彼此独立的线程里处理不同的请求,因为线程间的共享状态很少,这可以最小化同步。更多地,在多个独立的线程里执行应用逻辑允许开发者使用直观的线性指令和阻塞操作。
图2展示了一个使用同步线程设计的web服务器,它能够处理多个并发的客户端。这个图还展示了Sync Acceptor对象,它总结了服务端同步接受网络连接模型。使用每个线程执行一个http get请求,即每个连接一个线程( a Thread Per Connection)的这种并发模型的一系列步骤可以总结如下:
1,每个线程同步阻塞在accept函数来等待一个客户端连接请求。
2,一个客户端连接服务器并且这个连接被接受。
3,这个客户端的http请求被同步的从网络连接中读取。
4,请求被解析。
5,请求文件被同步的读取。
6,文件被同步的发送给客户端。

一个c++代码示例提供了一个web服务器的同步线程模型,在附录A.1。
如上所述,每个并发连接的客户端会被一个专门的服务端线程处理。在处理其他http请求之前,线程必须同步地完成这个请求操作。因此,为了在处理多个客户端时执行同步io,web服务器必须创建大量线程。尽管同步多线程模型很直观,而且在多cpu平台的效率很高,它有如下缺点:
线程策略和并发策略耦合严重:这种架构需要为每个连接的客户端配一个专门的线程。并发应用最好是通过可用资源来调整它的线程策略(如通过cpu数量调整线程池大小)而不是根据并发处理的客户端数量;
增加了同步的复杂性:线程会增加同步模型的复杂性,它需要串行处理服务器的共享资源(如缓存文件和web日志);
增加了性能开销:线程可能会表现很差,由于上下文切换,同步,cpu间的数据传输。
不可移植:并非所有操作系统都支持线程。更多地,从抢占式和非抢占式的角度来说,操作系统平台的表现有很大的不同。因此,很难构建一个表现一致的跨平台多线程服务器。
因为这些缺点,多线程通常既不是最高效也不是最简单的方案去设计一个并发web服务器。

2.2.2 通过反应式的同步事件分发器来并发
另一种常用的实现一个同步web服务器的方式是使用反应式分发器( reactive event dispatching)模型。reactor模式描述了应用如何在一个Initiation Dispatcher注册Event Handlers。当可能要初始化一个非阻塞操作时,Initiation Dispatcher会通知Event Handler。
一个单线程并发web服务器能够使用反应式事件分发器模型,它会在一个事件循环等待reactor通知它开始一个适当的操作。在web服务器上的一个反应式操作的例子是在Initiation Dispatcher上注册Acceptor。当网络连接上的数据到达时,分发器会调用Acceptor。Acceptor接受网络连接并且创建一个HTTP Handler。HTTP Handler会在Reactor上注册,并通过web服务器的单线程处理即将到来的URL请求连接。
图3和图4展示了如何设计一个使用反应式事件分发器处理多客户端的web服务器。图3展示了一个客户端连接web服务器的步骤。图4展示了web服务器如何处理一个客户端请求。图3的一系列步骤可以被总结如下:

1,web服务器在Initiation Dispatcher上注册Acceptor用来接受新连接。
2,web服务器发起Initiation Dispatcher的事件循环。
3,一个客户端连接到web服务器。
4,Initiation Dispatcher的新连接请求通知Acceptor,然后Acceptor接受新连接。
5,Acceptor创建一个HTTP Handler用来处理新的客户端。
6,HTTP Handler在Initiation Dispatcher上注册连接用来读取客户端的请求数据(即当连接变得读就绪)。
7, HTTP Handler处理来自新客户端的请求。
图4展示了反应式web服务器处理HTTP GET请求的一系列步骤。这个过程可被描述如下:
1,客户端发送HTTP GET。
2,当客户端的请求数据到达服务端时,Initiation Dispatcher通知HTTPHandler。
3,请求通过非阻塞方式读取。如果操作可能会导致线程阻塞,read操作会返回EWOULDBLOCK。(步骤2和步骤3会重复直到请求被完全读完)
4,HTTP Handler解析HTTP请求
5,请求文件会同步从文件系统中读取。
6, HTTP Handler在 Initiation Dispatcher上注册连接用来发送文件数据(即连接变得写就绪)
7,当TCP连接写就绪时,Initiation Dispatcher通知HTTP Handler。
8,HTTP Handler通过非阻塞方式发送请求文件给客户端,如果请求可能会引起阻塞那么写操作会返回EWOULDBLOCK(步骤7和8将会重复直到数据被完全发完)
一个c++代码示例提供了一个web服务器的反应式事件分发器模型,在附录A.2。
由于 Initiation Dispatcher运行在单线程,处于Reactor控制之下的网络IO操作需要以非阻塞的方式。如果在并发操作中进展失速,操作会被移交给初始事件分发器,有它来监控系统操作的状态。当操作使得进展继续,event handler会被再次通知。
反应式模型的主要优势是可移植、低开销由于粗暴的并发控制(也就是说单线程不需要同步和上下文切换)、模块化通过应用逻辑和分发器模型之间的解耦。然而,这种方式有如下的缺点:
编程复杂:如上所列,程序员必须写复杂的逻辑来确保服务器处理某个特定的客户端时不会阻塞。
缺少系统多线程支持:大多数操作系统实现反应分发器通过系统调用select。然而,select不能在多个线程的事件循环中等待同一个描述符集合。这使得反应式模型不能适应高性能应用,因为它不能高效地利用硬件并行。
运行的任务的调度:同步多线程架构支持抢占式线程,它由操作系统负责调度和时间分割运行的线程在可用的cpu之上。在反应式架构中不支持这种调度,因为它是在应用中是单线程。因此,这种系统的开发者必须小心地为线程在所有的连接web服务器的客户端之间分配时间。它将在很短的时间间隔被非阻塞式地执行完成。
由于这些缺点,当硬件支持并发时反应式事件分发器并不是最高效的模型。这种模型也需要很高编程复杂性因为它要避免阻塞IO。

2.3 解决方案:通过异步操作并发
当操作系统平台支持异步操作时,一种高效而且方便的方式去实现一个高性能web服务器的方法是使用主动式事件分发器。使用主动事件分发器实现的web服务器模型处理一或多个线程控制的异步操作的完成。因此,主动器模式简化了异步web服务器通过整合完成事件复用和事件处理分发器。
一个异步web服务器能够利用主动器模式通过首先让web服务器发出一个异步操作向操作系统,并且注册一个回调函数在完成分发器,它将通知web服务器当操作完成的时候。然后操作系统将代替web服务器执行操作,并且随后将结果排在一个周知的地方。完成分发器负责从队列中取出完成通知并且执行对应的回调,它将包含特定应用的web服务器代码。
图5和图6展示了使用主动事件分发器设计的web服务器处理多个并发客户端没有使用多线程。图5展示了一个客户端连接web服务器的一系列步骤。


1,web服务器发出指令,acceptor初始化一个异步accept。
2,acceptor初始化一个异步accept通过操作系统并且传递自身作为一个完成事件处理器,并且一个完成事件处理器的引用将被通知给acceptor直到异步accept完成。
3,web服务器调用完成事件分发器的事件循环。
4,客户端连接到web服务器。
5,当一个异步accept操作完成,操作系统通知事件完成分发器。
6,完成事件分发器通知acceptor。
7,acceptor创建一个http 处理器。
8,http处理器初始化一个异步操作去读取请求数据从客户端,并且传递自身作为一个完成事件处理器,并且一个完成事件分发器将被用于通知http 处理器直到异步读取完成。
图6展示了主动式web服务器执行http get请求的一系列步骤,这些步骤可被解释如下:
1,客户端发送http get请求。
2,读操作完成,操作系统通知完成事件分发器。
3,完成事件分发器通知http处理器(步骤2和3将重复直到整个请求都被接收)。
4,http处理器解析请求。
5,http处理器同步读取被请求的文件。
6,http处理器初始化一个异步操作去写文件数据到客户端连接,并且传递自身作为一个事件完成处理器和一个引用到完成事件分发器,当异步写事件完成它将被用于通知http处理器。
7,当写操作完成,操作系统通知事件完成分发器。
8,完成事件分发器然后通知完成事件处理器(步骤6和8将持续直到文件被传输完成)。
一个c++代码示例提供了主动式事件分发器模型的web服务器,在第八节。
主动器模式的主要优势是多个并发操作可以并行地被启动和运行而不需要应用有多个线程。操作可以异步的启动通过应用,而且他们运行完成在操作系统的子io系统之下。初始操作的线程将能够去处理另外的请求。
比如说上面的例子,完成事件分发器是单线程的。当http请求到达,单线程的完成事件分发器解析请求,读文件,并且发送回应给客户端。因为响应发送是异步的,多个响应能够同时潜在被发送。更多地,同步文件读可以被异步文件读代替来发掘并发的潜力。如果文件读被异步执行,只有http协议请求解析这一个操作是被http handler同步执行的。
主动器模型的主要缺点是编程逻辑至少跟反应式模型一样复杂。更多的,主动器模式很难调试因为异步操作是一种无法预测也无法重复执行的序列,这复杂化了分析和调试。第七节描述了如何应用其他模式(比如异步完成token)去简化异步应用编程模型。

3 适用性
使用主动器模式应该保证下面一或多个条件:
应用需要执行一或多个异步操作不能在阻塞线程中。
应用必须被通知当异步操作完成时。
应用需要变化自身并发策略,独立于自身io模型。
应用将收益于应用依赖的逻辑和应用独立的基础架构之间的解耦。
一个应用将表现很差或失败去满足自身性能要求当使用多线程方法或者反应式事件分发器。

4 数据结构和组件
主动器模式的数据结构如图7,使用OMT符号。
主动器模式的关键组件包括如下:
主动式初始器(web服务器应用的主要线程):
主动式初始器是任何实体在初始一个异步操作的应用。主动式初始器注册一个事件完成器并且一个完成分发器用一个异步操作处理器,它将通知事件分发器当操作完成时。
完成事件处理器(acceptor和http处理器):
主动器模式使用完成处理器接口,它们将被应用实现,用于异步操作的完成通知。
异步操作(异步read、write、accept方法):
异步操作都用于执行请求(比如说io和定时器操作)代表应用。当应用发起异步操作,操作被执行在不占据线程控制权的情况下。因此,从应用的角度,操作被异步的执行。当异步操作完成时,异步操作处理器委派代表应用通知一个完成分发器。
异步操作处理器(操作系统):
异步操作应该被运行完成通过异步操作处理器。这个组件的典型实现是操作系统。
完成分发器(通知队列):
完成分发器负责调用应用完成处理器当异步操作完成时。当异步操作处理器完成一个异步发起操作,完成分发器执行一个应用回调。

5 协作
对于全部的异步操作,有一些已经定义好的步骤。在一种高层次的抽象,应用初始异步操作并且被通知当操作完成。图8展示了各个组件之间的关系:
1,主动式启动器初始操作:
为了执行异步操作,应用在异步操作处理器上初始一个操作。举个例子,一个web服务器可能要求操作系统传输一个文件在网络上使用一个特定的socket连接。为了执行这个操作,web服务器必须指定哪个文件和网络连接去使用。更多地,web服务器必须指定哪个完成处理器来通知当操作完成而且哪个完成分发器应该执行这个回调一旦文件被传输。
2,异步操作处理器执行操作:
当应用在异步操作处理器上发起操作,它会异步地运行它以及其他应用操作。现代操作系统(比如solaris和windows nt)提供了异步io子系统在内核里。
3,异步操作处理器通知事件分发器:
当操作完成,异步操作处理器取回在操作初始时指定的完成事件处理器和完成事件分发器。异步操作处理器然后传递异步操作的结果和完成处理器给完成事件分发器用来回调。举个例子,如果一个文件被异步传输,异步操作处理器可能报道完成状态(比如说成功或失败),以及写入到网络上的的字节数。
4,完成分发器通知应用:
完成事件分发器调用完成钩子通过完成事件处理器,把任何被应用指定的完成数据传递给它。举个例子,如果异步读完成,完成处理器通常将被传递一个指向新到达的数据的指针。

6 结论
这一节描述使用主动器模式的一些总结。
6.1 好处
主动器模式有一下好处:
增强了分离关注点:主动器模式解耦了异步应用模型和应用特定功能。独立应用模型成为可复用组件,它知道如何去多路分配完成事件与异步操作和分发适当的被完成处理器定义的回调方法。同样地,应用特定功能知道如何去执行一个的特定类型的服务(比如http处理)
提高了应用逻辑的移植性:
提高了应用逻辑的移植性,通过运行独立复用的操作系统之下的接口,它执行事件多路分配。这些系统调用探测和报道事件可能同时发现在多路事件源上。事件源可能包括io端口,计时器,同步对象,信号等。在实时posix平台上,异步io函数被aio家族api提供。在windows nt,io完成端口和io复用被用于实现异步IO。
完成事件分发器封装了并发模型:
一个好处是解耦了完成事件分发器和异步操作处理器,应用可以配置完成分发器用不同的并发策略而不会影响其他组件。在第7节,完成分发器可以被配置用于几种并发策略包括单线程和线程池方案。
提高了性能:
多线程操作系统执行上下文切换去循环通过多线程控制。执行上下文切换的事件保持公正的常量,总共的时间去循环通过大量线程可以明显的降低应用性能如果操作系统上下文切换在一个空闲的线程。举个例子,线程可能轮询操作系统完成状态,这是低效的。主动器模式避免了上下文开销通过激活有事件需要去处理的逻辑线程控制。举个例子,一个web服务器不需要激活一个http处理器如果没有等待的get请求。
简化了应用同步:
因为完成处理器不需要扩张额外的控制线程,应用逻辑可以被实现很少或者不涉及同步问题。完成处理器可以被写好像他们存在一个方便的单线程环境一样。举个例子,一个web服务器get处理器可以处理硬盘通过一个异步读操作(比如说windows nt transmitFile函数)

6.2 缺点
主动器模式有如下缺点:
难以调试:用主动器模式写的应用很难调试,因为反转了基础框架和应用特定回调函数处理器之间的控制流。这增加了单步的难度,通过在一个调试框架中的实时行为,因为开发者可能不能理解或者不能进入框架代码。这有些相似的问题是促进调试一个编译器字典分析器和解析器通过LEX和YACC。在这些应用中,调试是直接了当的当线程控制在一个用户定义的行为程序时。一旦线程控制返回给通常的确定有限状态自动机框架,然而,这将难于理解程序逻辑。
调度和控制未完成的操作:主动式发动器可能没有控制权当异步操作被执行时。因此异步操作处理器必须被小心的设计来支持异步操作的优先顺序和取消。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值