事件驱动模型和反应器模式有何区别?

72

来自维基百科Reactor Pattern文章:

反应器设计模式是一种事件处理模式,用于通过一个或多个输入同时向服务处理程序传递服务请求。

它命名了一些例子,例如nodejstwistedeventmachine

但我理解上述内容是流行的事件驱动框架,因此它们也是反应器模式框架吗?

如何区分这两者?或者它们是相同的吗?

3个回答

124

反应器模式比“事件驱动编程”更具体。它是在进行事件驱动编程时使用的一种特定实现技术。但是,在典型对话中很少准确使用这个术语,因此您在使用它并期望您的听众理解时应该小心,当您遇到其使用时,您也应该小心如何解释这个术语。

观察反应器模式的一种方式是将其视为与“非阻塞”操作的概念密切相关。当某些操作可以在没有阻塞的情况下完成时,反应器会发送通知。例如,select(2) 可以用于使用标准 BSD 套接字 API (recv(2)send(2) 等) 实现用于从套接字读取和写入的反应器模式。 select 将告诉您什么时候可以立即从套接字接收字节——例如,因为这些字节在该套接字的内核接收缓冲区中存在。

考虑这些思想时,您可能还想考虑另一种模式,即 proactor 模式。与反应器模式相比,proactor 模式会使操作启动,无论它们是否能够立即完成,都会异步执行,并安排通知其完成。

Windows I/O 完成端口 (IOCP) API 是一个例子,可以看到其中的 proactor 模式。在使用 IOCP 对套接字进行发送时,无论该套接字的内核发送缓冲区中是否有空间,发送操作都会启动。发送操作继续进行(可能是在另一个线程中,例如内核中的线程),而WSASend 调用会立即完成。当发送实际完成时(这意味着被发送的字节已被复制到该套接字的内核发送缓冲区中),将调用传递给 WSASend 调用的回调函数(在应用程序中的新线程中)。

这种启动操作并在其完成时得到通知的方法是异步操作的核心思想。与非阻塞操作相比,非阻塞操作需要在尝试执行操作之前等待操作可以立即完成。

这两种方法都可用于事件驱动编程。使用反应器模式,程序等待(例如)套接字可读事件,然后从中读取数据。使用proactor模式,程序则等待套接字读取完成事件。

严格来说,Twisted误用了“反应器”这个术语。基于select(2)twisted.internet.selectreactor)的Twisted反应器使用非阻塞I/O实现,非常类似于反应器。但是,它向应用程序代码公开的接口是异步的,使其更像proactor。Twisted还有一个基于IOCP的反应器。该反应器公开相同的异步应用程序接口并且使用类似proactor的IOCP API。这种混合方法因平台而异,其细节不同,使“反应器”或“proactor”这两个术语都不太准确,但由于twisted.internet.reactor公开的API基本上完全是异步的而不是非阻塞的,因此“proactor”可能是更好的名称选择。


1
  1. 我可以这样说吗:反应器模式是一种异步排队事件的模式,但是同步地进行多路复用和分发;而Proactor模式则是将它们全部异步排队、多路复用和分发?
  2. 考虑到select(2) API,在使用事件编程时,现代库(如libevent)添加了什么好处?(我知道这与本问题无关,但作为您在这个领域的专家...)谢谢。
- Howard
这是维基百科页面的说法,所以我不敢轻易地说“不”。然而,作为一名从业者,我并没有在现实世界中遇到过人们使用这种措辞。由于这些术语相当晦涩,而且在使用时经常被误用,我认为你可以安全地说出这样的话,但也可以安全地说出其他话(比如反应堆->非阻塞,主动器->异步)。也许这两者之间甚至没有那么大的差距(你可以想象“同步”意味着“同步、非阻塞”——毕竟,“同步、阻塞”会干扰反应堆的进一步操作)。 - Jean-Paul Calderone
4
选择(2)函数是非常底层且难以使用的,对于大量事件源来说也效率低下。现代平台已经超越了它,提供了等价但更高效的API(如/dev/poll、epoll、kqueue等)。libevent(或Twisted)的一个优点是抽象出了所有这些不同的机制,因此应用程序可以编写到一个统一的API上,同时仍然从运行平台上最有效的API中受益。此外,还有明显的额外功能,例如协议实现、管理异步性的高级API等。 - Jean-Paul Calderone
1
有关select(2)和epoll(4)的比较,请参见https://dev59.com/P3I-5IYBdhLWcg3wED9V#2033785。 - Jean-Paul Calderone
那么,eventmachine 是否基于 Proactor 模式,因为它可以异步执行操作? - hololeap
1
如果你用 select从套接字接收字节 来解释反应器模式,那么你应该使用它们来解释 proactor 模式。否则,这会非常令人困惑,因为情景中有多个变化,对比变得太模糊了;实际上有两种不同的情景。你应该只使用一种! - Nawaz

6

我认为将“非阻塞”和“异步”分开是不正确的,因为“异步”的主要含义是“非阻塞”。反应器模式是关于异步调用(因此是非阻塞的),但是同步(阻塞)处理这些调用。Proactor是关于异步(非阻塞)调用和异步(非阻塞)处理这些调用。


1

为了处理TCP连接,有两种竞争的Web架构,分别是基于线程的架构和事件驱动的架构。

基于线程的架构

实现多线程服务器的最古老方式是采用“每个连接一个线程”的方法。为了控制和限制运行线程的数量,可以使用单个调度程序线程以及有界阻塞队列和线程池。

调度程序在TCP套接字上阻塞以获取新连接,并将它们提供给有界阻塞队列。超过队列限制的TCP连接将被丢弃,允许已接受的连接以期望和可预测的延迟运行。

事件驱动架构

将线程从连接中分离出来,事件驱动架构只允许将线程用于特定处理程序的事件。

这种创造性的概念使得反应器模式得以展示。基于这种架构构建的系统由事件创建者和事件消费者组成。

反应器模式

反应器模式是处理TCP连接的事件驱动架构的最流行实现技术。简单来说,它使用单线程事件循环,在事件上阻塞并将这些事件分派到相应的处理程序。

只要为事件注册了处理程序,其他线程就无需在I/O上阻塞。对于TCP连接,我们可以轻松地将事件引用到这些实例:已连接、输入准备就绪、输出准备就绪、超时和已断开。

反应器模式将模块化的应用级代码与可重用的反应器实现解耦。为了实现这一点,反应器模式的架构由两个重要的参与者组成——反应器和处理程序。

反应器

反应器在单独的线程中运行,并通过将工作分派给适当注册的处理程序来响应I/O事件,如已连接、输入准备就绪、输出准备就绪、超时和已断开。

处理程序

处理程序执行实际工作或需要完成的I/O事件响应。反应器通过分派适当的处理程序响应I/O事件。

《程序设计的模式语言》是Jim Coplien和Douglas C. Schmidt于1995年出版的一本详细介绍反应器模式的书籍。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接