123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- [/
- / Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
- /
- / 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)
- /]
- [section:async The Proactor Design Pattern: Concurrency Without Threads]
- The Boost.Asio library offers side-by-side support for synchronous and asynchronous
- operations. The asynchronous support is based on the Proactor design pattern
- [link boost_asio.overview.core.async.references \[POSA2\]]. The advantages and
- disadvantages of this approach, when compared to a synchronous-only or Reactor
- approach, are outlined below.
- [heading Proactor and Boost.Asio]
- Let us examine how the Proactor design pattern is implemented in Boost.Asio,
- without reference to platform-specific details.
- [$boost_asio/proactor.png]
- [*Proactor design pattern (adapted from \[POSA2\])]
- [mdash] Asynchronous Operation
- [:Defines an operation that is executed asynchronously, such as an asynchronous
- read or write on a socket.]
- [mdash] Asynchronous Operation Processor
- [:Executes asynchronous operations and queues events on a completion event
- queue when operations complete. From a high-level point of view, internal
- services like `reactive_socket_service` are asynchronous operation processors.]
- [mdash] Completion Event Queue
- [:Buffers completion events until they are dequeued by an asynchronous event
- demultiplexer.]
- [mdash] Completion Handler
- [:Processes the result of an asynchronous operation. These are function
- objects, often created using `boost::bind`.]
- [mdash] Asynchronous Event Demultiplexer
- [:Blocks waiting for events to occur on the completion event queue, and returns
- a completed event to its caller.]
- [mdash] Proactor
- [:Calls the asynchronous event demultiplexer to dequeue events, and dispatches
- the completion handler (i.e. invokes the function object) associated with the
- event. This abstraction is represented by the `io_context` class.]
- [mdash] Initiator
- [:Application-specific code that starts asynchronous operations. The initiator
- interacts with an asynchronous operation processor via a high-level interface
- such as `basic_stream_socket`, which in turn delegates to a service like
- `reactive_socket_service`.]
- [heading Implementation Using Reactor]
- On many platforms, Boost.Asio implements the Proactor design pattern in terms
- of a Reactor, such as `select`, `epoll` or `kqueue`. This implementation
- approach corresponds to the Proactor design pattern as follows:
- [mdash] Asynchronous Operation Processor
- [:A reactor implemented using `select`, `epoll` or `kqueue`. When the reactor
- indicates that the resource is ready to perform the operation, the processor
- executes the asynchronous operation and enqueues the associated completion
- handler on the completion event queue.]
- [mdash] Completion Event Queue
- [:A linked list of completion handlers (i.e. function objects).]
- [mdash] Asynchronous Event Demultiplexer
- [:This is implemented by waiting on an event or condition variable until a
- completion handler is available in the completion event queue.]
- [heading Implementation Using Windows Overlapped I/O]
- On Windows NT, 2000 and XP, Boost.Asio takes advantage of overlapped I/O to
- provide an efficient implementation of the Proactor design pattern. This
- implementation approach corresponds to the Proactor design pattern as follows:
- [mdash] Asynchronous Operation Processor
- [:This is implemented by the operating system. Operations are initiated by
- calling an overlapped function such as `AcceptEx`.]
- [mdash] Completion Event Queue
- [:This is implemented by the operating system, and is associated with an I/O
- completion port. There is one I/O completion port for each `io_context`
- instance.]
- [mdash] Asynchronous Event Demultiplexer
- [:Called by Boost.Asio to dequeue events and their associated completion
- handlers.]
- [heading Advantages]
- [mdash] Portability.
- [:Many operating systems offer a native asynchronous I/O API (such as
- overlapped I/O on __Windows__) as the preferred option for developing high
- performance network applications. The library may be implemented in terms of
- native asynchronous I/O. However, if native support is not available, the
- library may also be implemented using synchronous event demultiplexors that
- typify the Reactor pattern, such as __POSIX__ `select()`.]
- [mdash] Decoupling threading from concurrency.
- [:Long-duration operations are performed asynchronously by the implementation
- on behalf of the application. Consequently applications do not need to spawn
- many threads in order to increase concurrency.]
- [mdash] Performance and scalability.
- [:Implementation strategies such as thread-per-connection (which a
- synchronous-only approach would require) can degrade system performance, due to
- increased context switching, synchronisation and data movement among CPUs. With
- asynchronous operations it is possible to avoid the cost of context switching
- by minimising the number of operating system threads [mdash] typically a
- limited resource [mdash] and only activating the logical threads of control
- that have events to process.]
- [mdash] Simplified application synchronisation.
- [:Asynchronous operation completion handlers can be written as though they
- exist in a single-threaded environment, and so application logic can be
- developed with little or no concern for synchronisation issues.]
- [mdash] Function composition.
- [:Function composition refers to the implementation of functions to provide a
- higher-level operation, such as sending a message in a particular format. Each
- function is implemented in terms of multiple calls to lower-level read or write
- operations.]
- [:For example, consider a protocol where each message consists of a
- fixed-length header followed by a variable length body, where the length of the
- body is specified in the header. A hypothetical read_message operation could be
- implemented using two lower-level reads, the first to receive the header and,
- once the length is known, the second to receive the body.]
- [:To compose functions in an asynchronous model, asynchronous operations can be
- chained together. That is, a completion handler for one operation can initiate
- the next. Starting the first call in the chain can be encapsulated so that the
- caller need not be aware that the higher-level operation is implemented as a
- chain of asynchronous operations.]
- [:The ability to compose new operations in this way simplifies the development
- of higher levels of abstraction above a networking library, such as functions
- to support a specific protocol.]
- [heading Disadvantages]
- [mdash] Program complexity.
- [:It is more difficult to develop applications using asynchronous mechanisms
- due to the separation in time and space between operation initiation and
- completion. Applications may also be harder to debug due to the inverted flow
- of control.]
- [mdash] Memory usage.
- [:Buffer space must be committed for the duration of a read or write operation,
- which may continue indefinitely, and a separate buffer is required for each
- concurrent operation. The Reactor pattern, on the other hand, does not require
- buffer space until a socket is ready for reading or writing.]
- [heading References]
- \[POSA2\] D. Schmidt et al, ['Pattern Oriented Software Architecture, Volume
- 2]. Wiley, 2000.
- [endsect]
|