前摄器设计模式:无线程并发(The Proactor Design Pattern: Concurrency Without Threads)
Boost.Asio
Boost.Asio 库为同步和异步操作提供并排支持。异步支持基于监考器设计模式[POSA2]。与同步或反应器方法相比,这种方法的优点和缺点概述如下。
前摄器和 Boost.Asio
让我们来研究一下前摄器设计模式是如何在 Boost.Asio 中实现的,而不提及特定于平台的详细信息。

前摄器设计模式(改编自[POSA2])
- 异步操作(Asynchronous Operation)
定义异步执行的操作,例如异步读读或在套接字上写入。
- 异步操作处理器(Asynchronous Operation Processor)
执行异步操作,并在操作完成时对完成事件队列中的事件进行排队。从高层次的角度来看,像reactive_socket_service
这样的内部服务是异步操作处理器。
- 完成事件队列(Completion Event Queue)
缓冲完成事件,直到它们被异步事件多路分配器(demultiplexer)移出队列。
- 完成处理程序(Completion Handler)
处理异步操作的结果。这些是函数对象,通常使用boost::bind
创建。
- 异步事件多路分配器(Asynchronous Event Demultiplexer)
阻塞并等待完成事件队列上发生的事件,并将完成的事件返回其调用者。
- 前摄器(Proactor)
调用异步事件多路分配器将事件出列,并分派与事件关联的完成处理程序(即调用函数对象)。这个抽象由io_context
类表示。
- 发起者(Initiator)
启动异步操作的应用程序特定代码。启动器通过高级接口(如basic_stream_socket
)与异步操作处理器进行交互,而高级接口又委托给 (如reactive_socket_service
)等服务。
使用反应器实现
在许多平台上,Boost.Asio 在反应器(如select
, epoll
或kqueue
)中实现前摄器设计模式。此实现方法对应于前摄器设计模式,如下所示:
- 异步操作处理器(Asynchronous Operation Processor)
使用select
, epoll
或kqueue
实现的反应器。当反应器指示资源已准备好执行操作时,处理器将执行异步操作,并将关联的完成处理程序入列(enqueue)完成事件队列。
- 完成事件队列(Completion Event Queue)
完成处理程序(即函数对象)的链表。
- 异步事件多路分配器(Asynchronous Event Demultiplexer)
这是通过等待事件或条件变量实现的,直到完成处理程序在完成事件队列中可用。
使用 Windows 重叠 I/O 实现
在 Windows NT、2000 和 XP 上,Boost.Asio 利用重叠 I/O 提供前摄器设计模式的高效实现。此实现方法对应于前摄器设计模式,如下所示:
- 异步操作处理器(Asynchronous Operation Processor)
这是由操作系统实现的。操作通过调用重叠函数(如AcceptEx
)启动。
- 完成事件队列
这由操作系统实现,并关联到 I/O 完成端口。每个io_context
实例都有一个 I/O 完成端口。
- 异步事件多路分配器(Asynchronous Event Demultiplexer)
由 Boost.Asio 调用,使事件及其关联的完成处理程序出列。
优点
- 可移植性(Portability)。
许多操作系统提供本机异步 I/O API(如 Windows 上的重叠I/O)作为开发高性能网络应用程序的首选选项。该库可以根据本机异步 I/O 实现。但是,如果本机支持不可用,则库也可以使用表示反应器模式的同步事件分位器实现,例如POSIX select()
- 将线程与并发分离(Decoupling threading from concurrency)。
长期操作由实现代表应用程序异步执行。因此,应用程序不需要生成许多线程来增加并发性。
- 性能和可扩展性(Performance and scalability)。
由于 CPU 之间的上下文切换、同步和数据移动增加,实现策略(如每连接线程线程)(仅同步方法需要)可能会降低系统性能。使用异步操作,可以最大限度地减少操作系统线程的数量(通常是有限的资源)并仅激活具有要处理事件的逻辑控制线程,从而避免上下文切换的成本。
- 简化应用程序同步(Simplified application synchronisation)。
异步操作完成处理程序可以像存在于单线程环境中一样编写,因此可以开发应用程序逻辑,而很少或不关心同步问题。
- 函数组成(Function composition)。
函数组合是指函数的实现,以提供更高级别的操作,例如以特定格式发送消息。每个函数都是通过多次调用较低级别的读写操作实现的。
例如,考虑一个协议,其中每条消息由固定长度标头和可变长度正文组成,其中正文的长度在标头中指定。假设的read_message操作可以使用两个较低级别的读取实现,第一个读取接收标头,一旦知道长度,第二个读取接收正文。
若要在异步模型中组合函数,可以将异步操作链接在一起。也就是说,一个操作的完成处理程序可以启动下一个操作。可以封装链中的第一个调用,这样调用方就不需要意识到更高级别的操作是作为异步操作链实现的。
能够这样组成新操作,从而简化了网络库之上更高抽象级别的开发,例如支持特定协议的函数。
缺点
- 程序复杂性(Program complexity)。
由于操作启动和完成之间的时间和空间的分离,使用异步机制开发应用程序更加困难。由于控制流的倒置,应用程序也可能更难调试。
- 内存使用情况(Memory usage)。
在读取或写入操作期间必须提交缓冲区空间,此操作可能会无限期地继续,并且每个并发操作都需要单独的缓冲区。另一方面,在套接字准备好读取或写入之前,反应器模式不需要缓冲空间。
引用
[POSA2]: D. Schmidt et al, Pattern Oriented Software Architecture, Volume 2. Wiley, 2000.
Copyright © 2003-2020 Christopher M. Kohlhoff
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)