(Original)
I bought the 2007.4 issue of programmer magazine yesterday and read it for the first time. One of the two high-performance I/O design patterns is eye-catching, this is a translation. I have been trying to carefully read this article for a long time.
In this article, the I/O methods of the system can be divided into blocking, non-blocking synchronous and non-blocking asynchronous. Among the three methods, the non-blocking asynchronous mode has the best scalability and performance. I mainly talked about two Io multiplexing modes: reactor and proactor, and compared them.
This article also introduces how to build a universal, unified external interface for the reactor and proactor modes and a completely portable development framework selection solution: tproactor (ACE compatible proactor): http://www.terabit.com.au/solutions.php. Because Linux does not fully support AIO, The ace_proactor framework performs poorly in Linux. Most of the Code that runs normally on Windows is abnormal in Linux and cannot even be compiled. This problem has been plagued by many Ace users. Now, there is a tproactor that helps solve the problem of normal use (at least seemingly normal) of ace_proactor when Linux does not fully support AIO.
Abstr:
---------->
Two I/O multiplexing modes: reactor and proactor
Generally, the I/O multiplexing mechanism depends on an event demultiplexer ). The splitter object can separate I/O events from the event source and distribute them to the corresponding read/write event processor (event handler ). Developers pre-register the event to be processed and its event processor (or callback function); the event splitter is responsible for passing request events to the event processor. Two modes related to the event splitter are reactor and proactor. The reactor mode adopts synchronous Io, while the proactor adopts asynchronous Io.
In the reactor, the event splitter is responsible for waiting for the file descriptor or socket to prepare for the read/write operation, passing the ready event to the corresponding processor, and finally the processor is responsible for the actual read/write work.
In proactor mode, the processor, or the event splitter that serves as the processor, is only responsible for initiating asynchronous read/write operations. Io operations are completed by the operating system. The parameters passed to the operating system must include the user-defined data buffer address and data size. The operating system can obtain the data required for the operation or write the data read from the socket. The event splitter captures the IO operation completion event and then transmits the event to the corresponding processor. For example, in windows, the processor initiates an asynchronous Io operation, and the event splitter waits for the iocompletion event. The typical asynchronous mode implementation is based on the Operating System Supporting asynchronous APIs. We call this implementation "system-level" Asynchronous or "true" Asynchronous, because the application relies entirely on the operating system to execute real Io work.
For example, the difference between a reactor and a proactor can be understood. A read operation is used as an example (similar to a class operation ).
Implement read in Reactor:
-Register read-ready events and corresponding event Processors
-Event separator wait event
-When the event arrives, the separator is activated and the processor corresponding to the separator call event is activated.
-The event processor completes the actual read operation, processes the read data, registers a new event, and then returns control.
AndRead process in proactor (true asynchronous)Comparison:
-The processor initiates an asynchronous read operation (note: the operating system must support asynchronous Io ). In this case, the processor ignores the IO readiness event and focuses on the completion event.
-Event splitter waiting for Operation completion event
-When the splitter is waiting, the operating system uses parallel kernel threads to perform actual read operations, stores the result data into the User-Defined buffer zone, and finally notifies the event splitter to complete read operations.
-The event splitter calls the processor.
-The event processor processes data in the User-Defined buffer, starts a new Asynchronous Operation, and returns control to the event splitter.
Practice Status
The open-source C ++ development framework ace developed by Douglas Schmidt and others provides a large number of underlying classes (threads, mutex volumes, etc.) that are not related to the platform and support concurrency, and at a high abstraction level, it provides two sets of different classes-ace reactor and ACE proactor. However, although the two are not related to the platform, they provide different interfaces.
Ace proactor delivers better performance on Windows platforms because Windows provides efficient asynchronous API support in the operating system (see http://msdn2.microsoft.com/en-us/library/aa365198.aspx ).
However, not all operating systems support Asynchronization at the system level. As many UNIX systems do not. Therefore, it may be better to select the ace reactor solution on UNIX. But in this way, in order to achieve the best performance, network application developers must maintain multiple codes for different operating systems: Based on ACE proactor in windows, the ACE reactor solution is used on UNIX systems.
Improvement Plan
In this section, we will try to address the challenge of establishing a portable framework for the proactor and reactor modes. In the improvement scheme, we move the read/write operation that the reactor originally located in the event processor to the separator (This idea may be called "analog asynchronous "), in this way, we seek to convert the reactor Multi-Channel Synchronous Io into analog asynchronous Io. Taking the read operation as an example, the improvement process is as follows:
-Register the read-ready event and its processor, provide the data buffer address for the splitter, and read the data volume and other information.
-Separator wait event (for example, wait on select)
-When the event arrives, activate the separator. The splitter executes a non-blocking read operation (it has all the information required to complete this operation), and finally calls the corresponding processor.
-The event processor processes user-defined buffer data, registers new events (of course, it also provides the data buffer address, the data volume to be read, and other information), and finally returns the control to the splitter.
As we have seen, the reactor mode can be converted to the proactor mode by modifying the functional structure of the multi-channel Io mode. Before and after the transformation, the actual workload of the model has not increased, but the participants have slightly changed their responsibilities. Without changing the workload, it will naturally not weaken the performance. The comparison of the following steps proves that the workload is constant:
Standard/typical reactor:
-Step 1: Wait for the event to arrive (responsible for the reactor)
-Step 2: distribute read-ready events to user-defined processors (responsible for Reactor)
-Step 3: Read data (the user processor is responsible)
-Step 4: process data (the user processor is responsible)
Simulation proactor:
-Step 1: Wait for the event to arrive (the proactor is responsible)
-Step 2: Get the read-ready event and execute the read data (now the proactor is responsible)
-Step 3: distribute read completion events to the user processor)
-Step 4: process data (the user processor is responsible)
For operating systems that do not provide asynchronous Io APIs, this method can hide the interaction details of socket APIs and expose a complete asynchronous interface. In this way, we can further build completely portable, platform-independent, and general external interface solutions.
The above scheme has been implemented by Terabit P/L (http://www.terabit.com.au/) as tproactor. It has two versions: C ++ and Java. C ++ uses ACE as the underlying class for cross-platform development, providing a universal and unified active asynchronous interface for all platforms.
The boost. ASIO Library also adopts a similar solution to implement a unified Io asynchronous interface.
<-----------
Recently, the boost. ASIO class library has been used in the project. It is implemented in the proactor design mode. For details, refer:Proactor(The boost. ASIO library is based on the proactor pattern. This design note outlines the advantages and disadvantages of this approach.), its design documentation link: http://asio.sourceforge.net/boost_asio_0_3_7/libs/asio/doc/design/index.html
First, let us examine how the proactor design pattern is implemented in ASIO, without reference to platform-specific details.
Proactor design pattern (adapted from [1])
Of course, these two I/O design models are also widely used in ACE, which has been introduced in Ace-related books, there are many good articles on the "ace developer" website.
Example: Ace technology paper-Chapter 1 proactor: object behavior patterns used for asynchronous event multiplexing and dispatching Processors
Ace technology paper-Chapter 1 design and use of ACE reactors (Reactor): An Object-Oriented architecture for event multiplexing
Ace programmer tutorial-Chapter 1 reactor: Architecture mode for event Multi-Channel Separation and dispatch
Ace application-Chapter 4 Jaws: High-Performance Web Server Architecture
The proactor mode has unparalleled advantages in single-CPU Single-core system applications. The problem is: how can it better apply the advantages of multithreading in multiple-CPU multi-core systems ??? This is a valuable consideration and practice. Another design model may be created to adapt to the development needs.