Proactor设计模式:无线程的并发
Boost.Asio库同时支持同步和异步操作。其异步支持基于Proactor设计模式。与仅同步或Reactor方法相比,这种方法的优缺点如下。
Proactor与Boost.Asio 我们将探讨Boost.Asio中Proactor设计模式的实现,而不涉及特定平台的细节。

Proactor设计模式(改编自[POSA2])
— 异步操作 定义一个异步执行的操作,例如在套接字上进行异步读取或写入。
— 异步操作处理器 执行异步操作,并在操作完成时将事件排队到完成事件队列。从高层次来看,像reactive_socket_service这样的内部服务是异步操作处理器。
— 完成事件队列 缓冲完成事件,直到它们被异步事件解复用器出队。
— 完成处理程序 处理异步操作的结果。这些是函数对象,通常使用boost::bind创建。
— 异步事件解复用器 阻塞等待完成事件队列中的事件发生,并将完成事件返回给调用者。
— Proactor 调用异步事件解复用器以出队事件,并调度与该事件关联的完成处理程序(即调用函数对象)。该抽象由io_context类表示。
— 发起者 启动异步操作的特定于应用程序的代码。发起者通过基本流套接字等高层接口与异步操作处理器交互,这又委托给像reactive_socket_service这样的服务。
使用Reactor的实现 在许多平台上,Boost.Asio以Reactor的形式实现Proactor设计模式,例如select、epoll或kqueue。此实现方法与Proactor设计模式的对应关系如下:
— 异步操作处理器 使用select、epoll或kqueue实现的Reactor。当Reactor指示资源准备好执行操作时,处理器执行异步操作,并将相关的完成处理程序排队到完成事件队列。
— 完成事件队列 完成处理程序的链表(即函数对象)。
— 异步事件解复用器 通过等待事件或条件变量,直到完成事件队列中有完成处理程序可用。
使用Windows重叠I/O的实现 在Windows NT、2000及XP、Vista、7及更高版本中,Boost.Asio利用重叠I/O提供Proactor设计模式的高效实现。此实现方法与Proactor设计模式的对应关系如下:
— 异步操作处理器 由操作系统实现。通过调用如AcceptEx等重叠函数来发起操作。
— 完成事件队列 由操作系统实现,并与I/O完成端口关联。每个io_context实例有一个I/O完成端口。
— 异步事件解复用器 由Boost.Asio调用以出队事件及其关联的完成处理程序。
优点 — 可移植性 许多操作系统提供原生异步I/O API(如Windows的重叠I/O),作为开发高性能网络应用的首选选项。该库可以基于原生异步I/O实现。然而,如果没有原生支持,该库也可以使用典型于Reactor模式的同步事件解复用器实现,例如POSIX select()。
— 将线程与并发解耦 长时间操作由实现异步执行,因此应用程序无需生成多个线程以增加并发性。
— 性能和可扩展性 像每个连接一个线程的实现策略(同步方法所需)可能会降低系统性能,因上下文切换、同步和CPU间的数据移动增加。通过异步操作,可以通过最小化操作系统线程数量(通常是有限资源)来避免上下文切换的成本,只激活有事件需要处理的逻辑控制线程。
— 简化应用程序同步 异步操作完成处理程序可以像在单线程环境中那样编写,因此应用程序逻辑可以在几乎不考虑同步问题的情况下开发。
— 函数组合 函数组合指的是实现函数以提供更高层次的操作,例如以特定格式发送消息。每个函数都是通过多次调用较低层次的读写操作实现的。
例如,考虑一种协议,每个消息由固定长度的头部和可变长度的主体组成,主体的长度在头部中指定。一个假设的read_message操作可以使用两个较低层次的读取来实现,第一个接收头部,第二个在知道长度后接收主体。
在异步模型中组合函数时,可以将异步操作链接在一起。即,一个操作的完成处理程序可以启动下一个操作。可以封装开始链中第一个调用,以便调用者无需知道更高层次的操作是作为异步操作链实现的。
以这种方式组合新操作的能力简化了在网络库上开发更高层次抽象的过程,例如支持特定协议的函数。
缺点 — 程序复杂性 使用异步机制开发应用程序更为困难,因为操作启动和完成之间的时间和空间分离。由于控制流反转,应用程序也可能更难调试。
— 内存使用 在读取或写入操作持续期间,必须承诺缓冲区空间,而每个并发操作需要一个单独的缓冲区。另一方面,Reactor模式在套接字准备好读取或写入之前并不需要缓冲区空间。
参考文献 [POSA2] D. Schmidt等,模式导向软件架构,第2卷。Wiley,2000年。
Last updated