程序设计中的两大经典模式 — Reactor & Proactor

1. 引言

Reactor 与 Proactor 模型是近几年技术领域频频提到的两个设计模式,那么,究竟什么是 Reator,什么又是 Proactor,他们之间有什么异同呢?
本文就来详细介绍一下。

2. UNIX 下的五种 IO 模型

此前,我们已经介绍过 linux 系统中的五种 IO 模型:
IO复用 & UNIX下的五种IO模型

在 IO 模型中,IO 复用模型,例如 epoll、select 等就是在 Reator 思想下诞生的,而异步 IO 模型,例如 glibc 实现的 posix aio 或是 linux 原生的 libaio 就是在 Proactor 思想下诞生的。
如果你非常熟悉 IO 复用模型与异步 IO 模型之间的差异,那么,关于 Reactor 与 Proactor 思想的区别就非常清晰了。

3. Reactor 模式

3.1. 模式构成

Reactor包含以下角色:

  1. Handle 句柄 — 在 linux 中,就是常见的文件描述符,用来标识 socket 连接或是打开的文件

  2. Reactor — 反应器,定义抽象接口,实现:

    1. 供应用程序注册和删除关注的事件句柄

    2. 运行事件循环

    3. 有就绪事件到来时,分发事件到之前注册的回调函数上处理
      Synchronous Event Dispatcher — 同步事件多路分发器,由操作系统内核实现,用于阻塞等待发生在句柄上的一个或多个事件,我们系统中的 select、poll、epoll 等多路复用 IO 就充当了这一角色。

    3. Event Handler — 事件处理接口。

3.2. 工作时序

下面展示了整个 Reactor 模式工作的时序:

整体的思想分为以下几步:

  1. 初始化启动应用,将事件注册到 Reactor 中

  2. 调用 get_handle() 接口,获取事件处理对象

  3. 调用 Reactor 进入事件循环,等待注册的事件到来

  4. 注册的事件触发,select() 返回,Reactor 回调已注册的回调函数

这一思想就是基于经典的回调思想“不要调用我,让我来调用你”的“好莱坞法则”设计的,具体的执行过程可以参看 epoll 的使用

4. Proactor 模式

Proactor 模式是另一个消息异步通知的设计模式,与 Reactor 的最大区别在于,Proactor 通知的不是就绪事件,而是操作完成事件,这也就是操作系统异步 IO 的主要模型。

4.1. 模式构成

Proactor 模式包含以下角色:

  1. Handle 句柄 — 在 linux 中,就是常见的文件描述符,用来标识 socket 连接或是打开的文件

  2. Asynchronous Operation Processor — 异步操作处理器;负责执行异步操作,一般由操作系统内核实现,也可以被用户态线程或进程模拟

  3. Asynchronous Operation — 异步操作

  4. Completion Event Queue — 完成事件队列,用来缓存已经完成的异步操作

  5. Proactor — 主动器,定义抽象接口,实现:

    1. 为应用程序进程提供事件循环

    2. 从完成事件队列中取出异步操作的结果

    3. 分发调用已完成时间相应的后续处理逻辑

  6. Completion Handler — 完成事件接口,一般是由回调函数组成的接口。

  7. Concrete Completion Handler — 完成事件后的具体处理逻辑,实现接口定义特定的应用处理逻辑。

4.2. 模式执行时序

下图展现了 Proactor 执行的时序:

主要分为以下几步:

  1. 初始化启动,注册异步操作完成后的回调操作

  2. 主程序调用异步操作处理器提供的异步操作接口

  3. Asynchronous Operation Processor 执行异步操作,完成后将结果放入事件完成队列

  4. Proactor 从完成事件队列中取出结果,分发到相应的完成事件回调函数处理逻辑中

5. 优势与不足

5.1. 主动与被动 — Reactor 与 Proactor 的区别

Reactor 调用后,需要被动等待对象进入就绪状态,然后再进行后续处理。
Proactor 则会待操作完全完成后由内核返回,主进程可以主动切换去执行其他任务。

5.2. Reactor 的优势与不足

5.2.1. 优势

Reactor 在实现上相对比较简单,对于大量对象,频繁从非就绪态触发到就绪态的场景处理十分高效。
同时,操作系统可以同时去等待多个对象触发,并且可以在事件触发后自由地选择后续执行流程,具有很高的灵活性。
虽然并发编程实现阻塞式同步 IO 也可以实现同时等待多个对象触发的效果,但在编程的复杂度与资源的消耗等方面,Reactor 模式拥有明显的优势。

5.2.2. 不足

但是 Reactor 的不足也很明显,如果就绪态长时间没有触发,则进程一直等待,长时间阻塞主进程,影响到整个系统的吞吐。

5.3. Proactor 的优势与不足

此前我们介绍了 glibc 实现的 POSIX aio 与 linux 原生实现的 libaio,他们是典型的 Proactor 模式的处理模型:
POSIX AIO — glibc 版本异步 IO 简介
linux AIO — libaio 实现的异步 IO 简介及实现原理

5.3.1. 优势

Proactor 最显著的优势在于处理耗时长的 IO 操作和并发场景。
同时,针对 IO 操作,一旦提交,内核只有在完全执行完成后才会再次通知到用户进程,在这个过程中,用户进程可以做任何其他操作,这给与了用户进程更大的灵活性。

5.3.2. 不足

Proactor 的实现相对比较复杂,在实际编程中,与基本的同步 IO 相比,aio 在使用上也不那么容易,尤其是 linux 的 libaio 具有五个 api,同时需要自己构造执行上下文和 buffer,性能与 windows 下的 IOCP 相比也有一定的差距,普通场景中还是不建议使用 linux 的 aio 的。

6. 微信公众号

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤。

7. 参考资料

http://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf。
https://en.wikipedia.org/wiki/Reactor\_pattern。
https://en.wikipedia.org/wiki/Proactor\_pattern。
https://www.dre.vanderbilt.edu/~schmidt/PDF/Proactor.pdf。
http://lse.sourceforge.net/io/aio.html。

程序设计中的两大经典模式 — Reactor & Proactor》来自互联网,仅为收藏学习,如侵权请联系删除。本文URL:http://www.bookhoes.com/806.html