Proactor设计模式:单线程高并发
Boost::Asio为同步和异步操作提供了并行支持,异步支持基于前摄器模式,这种模式的优点和缺点可能比只同步或反应器方法要低。
让我们检查一下Boost::Asio是如何实现前摄器模式的,没有引用基于平台的细节。
前摄器设计模式,改编自POSA2
--异步操作
定义一个异步执行的操作,比如socket的异步读和异步写。
--异步操作处理器
执行异步操作并在操作完成的时候把事件入队到一个完成事件队列,stream_socket_service等服务可以抽象成异步操作处理器。
--完成事件队列
缓存完成事件直到他们被异步事件分解器取出队列。
--完成句柄
处理异步操作的结果,是函数对象,经常使用boost::bind创建。
--异步事件多路分解器
阻塞等待完成事件队列,直到有事件发生,并返回一个完成事件给调用者。
--前摄器
调用异步事件分解器来把事件出队列,并且分发给同事件关联的完成句柄(调用函数对象),这个抽象层的代表是io_service层。
--发起者
应用程序的代码启动了异步操作,发起者通过一个抽象的接口来和异步操作处理器交互,比如basic_stream_socket,轮流提供类似于stream_socket_service的服务。
使用Reactor的实现
在许多平台,Boost::Asio依照Reactor的方式实现Proactor,比如select、epoll、kqueue,下列方法符合Proactor的设计模式:
--异步操作处理器
一个使用select、epoll、kqueue实现的Reactor,当Reactor指示资源已经准备好执行操作,处理器执行操作并把完成时间队列关联的完成句柄入队列。
--完成事件队列
完成句柄的列表(函数对象)。
--异步事件多路分解器
等待事件或者条件变量直到完成事件队列的完成句柄可用。
使用windows平台overlapped I/O实现
在windowsNT、2000或者xp,Boost::Asio利用overlapped的优势来实现有效的Protacor模式,下列是符合Proactor模式的方法:
--异步操作处理器
操作系统提供支持,通过调用AcceptEx之类的操作来发起操作。
--完成事件队列
操作系统提供支持,并和I/O完成端口关联起来,
--异步事件多路分解器
被Boost::Asio调用来出队列事件和他们的关联完成句柄。
优势
--可移植的
许多操作系统支持一个原生的异步I/O API作为高性能网络开发的推荐操作,这些库可能依据原生异步I/O的实现,然而,如果原生API不可用,这些库也可能使用典型Reactor模式的异步事件多路分解器来实现,比如POSIX select()。
--从并发中解耦线程
耗时长的操作被应用程序异步执行,因此应用程序不需要启动大量线程来提高并发
--性能和扩展性
某些实现策略会降低系统性能,比如每个线程一个连接,因为增加了CPU间的上下文切换、同步、数据移动,通过异步操作可以避免或者减少上下文切换开销-最小化操作系统的线程-限制资源-只激活有事件要处理的控制线程。
--简单的应用同步
异步完成句柄就像是在一个单线程环境写入,因此应用程序开发过程中可以少考虑或者不考虑同步问题。
--函数构成
函数构成依据函数的实现来提高更高抽象的操作,比如发送一个特定格式的消息,每个函数多次调用来完成较低抽象的读、写操作。
例如,一个协议的每个消息包含一个定长的消息头和变长的消息体,消息体的长度被消息头指定,一种假设是用两个低抽象的读、写操作完成消息的读取,第一个接收消息头,一旦消息长度确定,第二个接收整个消息。
为了写作异步模式的函数,异步操作可以被连接起来,也就是说,一个操作上的完成端口可以启动另外一个,启动链条的第一个可以被包装起来,调用者不需要知道高级抽象操作作为一个异步操作的链条来实现。
这种编写方式简化了开发者在网络库上的高层抽象,比如为了支持某种协议而开发函数。
劣势
--编程复杂性
使用异步模式开发应用程序更加困难,因为从时间和空间来看启动和完成被分割开来,因为控制流反转应用程序更难被调试。
--内存使用
在读、写期间内存是一直占用的,这会带来不确定性,在每个并发的时候需要一个单独的buffer,而Reactor模式,在读、写就绪前并不需要缓存空间。
参考文献
[POSA2] D. Schmidt et al, Pattern Oriented Software Architecture, Volume 2. Wiley, 2000.