Asynchronous I/O and Completion Ports for System Programming
I. synchronous I/O and asynchronous I/O
Before introducing this part, let's first get to know "asynchronous I/O ".
Speaking of asynchronous Io, it is easy to think of synchronous I/O. For the same I/O object handle, only one I/O operation is allowed at the same time. The principle is shown in:
Obviously, when the kernel really processes I/O (T2 ~ T4), the user thread is in the waiting state, if this time period is relatively short, there is no impact; if this time period is long, the thread will be suspended for a long time. In fact, this thread can use this time to process other transactions.
Asynchronous I/O can solve the problem of synchronous I/O, and supports parallel processing of the same I/O object, as shown in the following figure:
When the asynchronous I/O request is complete, I/O objects or event objects can be trusted to notify the user thread, in the user thread, you can use getoverlappedresult to view the execution status of I/O.
Because asynchronous I/O will return immediately after I/O requests are made, the following question occurs: "How does a program obtain the results of I/O processing ?".
There are multiple ways to implement asynchronous I/O, and the categories of different data are generally different, but the principles are similar. Here I divide the methods for implementing asynchronous I/O into three categories, this article will discuss these three methods in detail.
(1) Overlapping I/O
(2) asynchronous process call (APC), extended I/O
(3) Use the completed port (iocp)
Ii. asynchronous I/O using overlapping I/O
The same thread can perform I/O operations on multiple I/O objects. Different threads can also operate on the same I/O object. In my understanding, this is the way the names overlap.
When overlapping I/O is used, the thread needs to create an overlapped structure for I/O processing. The most important member in this structure is hevent, which exists as a synchronization object. If hevent is null, at this time, the synchronization object is an I/O operation object such as a file handle and a pipeline handle. After I/O is complete, the synchronization object here will be sent to the user thread.
Because I/O requests are returned immediately, but sometimes the user thread needs to know the current execution status of I/O, you can use getoverlappedresult. If the bwait parameter of the function is true, the function will block the thread until the target I/O processing is completed. If the bwait parameter is false, the system will return immediately, if I/O at this time is not complete, call getlasterror to return error_io_incomplete.
Sample Code 1:
Code :--------------------------------------------------------------------------------
DWORD nreadbyte;
Byte bbuf [buf_size];
Overlapped ov = {0, 0, 0, 0, null}; // hevent = NULL;
Handle hfile = createfile (......, File_flag_overlapped ,...... );
Readfile (hfile, bbuf, sizeof (bbuf), & nreadbyte, & OV );
// Because hevent = NULL at this time, the synchronization object is hfile. The effect of the following two statements is the same.
Waitforsingleobject (hfile, infinite );
// Getoverlappedresult (hfile, & ov, & nread, true );
--------------------------------------------------------------------------------
This code will be returned immediately after the readfile is called, but it will be blocked in the subsequent waitforsingleobject or getoverlappedresult, and the synchronization object hfile will be used for synchronization.
This code can implement normal asynchronous I/O here, but there is a problem. If you need to perform multiple I/O operations on the hfile handle, the problem will occur. See the following code.
Sample Code 2:
Code :--------------------------------------------------------------------------------
DWORD nreadbyte;
Byte bbuf1 [buf_size], bbuf2 [buf_size], bbuf3 [buf_size];
Overlapped ov1 = {0, 0, 0, 0, null };
Overlapped ov2 = {0, 0, 0, 0, null };
Overlapped ov3 = {0, 0, 0, 0, null };
Handle hfile = createfile (......, File_flag_overlapped ,...... );
Readfile (hfile, bbuf1, sizeof (bbuf1), & nreadbyte, & ov1 );
Readfile (hfile, bbuf2, sizeof (bbuf2), & nreadbyte, & ov2 );
Readfile (hfile, bbuf3, sizeof (bbuf3), & nreadbyte, & ov3 );
// Assume that the three I/O processes take a long time and it is not over yet.
Getoverlappedresult (hfile, & ov1, & nread, true );
--------------------------------------------------------------------------------
There are three overlapping I/O operations for hfile, but their synchronization objects are all hfile. Getoverlappedresult is used to perform the wait operation. It seems to be waiting for the completion of the first I/O processing. In fact, this function will return if any one of the I/O processing is complete, it is equivalent to ignoring the results of the other two I/O operations.
In fact, there is a very important principle: when there is more than one I/O operation on an overlapping handle, we should use the event object instead of the file handle for synchronization. For the correct implementation, see Example 3.
Sample Code 3:
Code :--------------------------------------------------------------------------------
DWORD nreadbyte;
Byte bbuf1 [buf_size], bbuf2 [buf_size], bbuf3 [buf_size];
Handle hevent1 = createevent (null, false, false, null );
Handle hevent2 = createevent (null, false, false, null );
Handle hevent3 = createevent (null, false, false, null );
Overlapped ov1 = {0, 0, 0, 0, hevent1 };
Overlapped ov2 = {0, 0, 0, 0, hevent2 };
Overlapped ov3 = {0, 0, 0, 0, hevent3 };
Handle hfile = createfile (......, File_flag_overlapped ,...... );
Readfile (hfile, bbuf1, sizeof (bbuf1), & nreadbyte, & ov1 );
Readfile (hfile, bbuf2, sizeof (bbuf2), & nreadbyte, & ov2 );
Readfile (hfile, bbuf3, sizeof (bbuf3), & nreadbyte, & ov3 );
// At this time, the synchronization objects of the three I/O operations are hevent1, hevent2, and hevent3 respectively.
Getoverlappedresult (hfile, & ov1, & nread, true );
--------------------------------------------------------------------------------
In this way, the getoverlappedresult can be used to process the first I/O.
For details about overlapping I/O, refer to the named pipe instance in "Process Communication for Windows programming.
Http://bbs.pediy.com/showthread.php? S = & threadid = 26252.
Iii. Implement Asynchronous I/O using asynchronous process calls
An asynchronous process call (APC) is used to asynchronously execute a call in a specific context. APC can be used in asynchronous I/O, that is, the IO System of the operating system can call your program immediately after completing asynchronous I/O. (In some materials, the APC in asynchronous I/O is called a "completion routine". I think this name is more appropriate. The following is a "completion routine. In addition, APC is usually used as the thread to synchronize the content. Here, we try to fade this concept to avoid confusion. Details about APC can be further introduced during thread synchronization)
Note the following three points:
(1) APC is always called in the call thread;
(2) When APC is executed, the calling thread enters the variable wait state;
(3) The thread needs to use extended I/O SERIES functions, such as readfileex and writefileex, and variable wait functions are also required (at least one of the following ):
Waitforsingleobjectex
Waitformultipleobjectex
Sleepex
Signalobjectandwait
Msgwaitformultipleobjectsex
When readfileex and writefileex are used, hevent members in the overlapped structure do not have to be specified, because the system will ignore them. When multiple Io operations share the same completion routine, hevent can be used to carry serial numbers and other information to distinguish different I/O operations, because the overlapping structure is passed to the completion routine. If multiple I/O operations use different completion routines, you can directly set hevent to null.
There are two conditions for the system call completion routine:
(1) I/O operations must be completed
(2) The call thread is in the variable wait state.
The first condition is relatively easy. It is clear that the completion routine is called only when the I/O operation is completed. As for the second condition, the control needs to be considered. By using the variable wait function, let the call thread be in a variable wait state, so that the completion routine can be executed. Here, you can control the execution of the completion routine by adjusting the time when the variable wait function is called. This ensures that the completion routine will not be executed too early.
When a thread has multiple completion routines, a queue is formed. When a thread enters the variable wait state using the variable wait function, there is a parameter indicating the timeout value. If infinite is used, then, the wait function is returned only when all queuing completion routines are executed or the handle gets the signal.
The preceding section briefly describes some important details about how to use the completed routine to Implement Asynchronous I/O. Next, we will use an example to illustrate the specific implementation process of the completed routine.
Example 1: asynchronous I/O example of the completion routine
1. design objectives
Realize the asynchronous I/O implementation principle and process of the routine.
2. Problem Analysis and Design
The design flowchart is as follows:
Graph description:
The three Io operations are respectively io_a, io_ B, and io_c. Their completion routines are apc_a, apc_ B, and apc_c. Io_a and io_ B are two very short Io operations, and io_c is a relatively time-consuming Io operation.
3. Detailed Design (the key code is as follows. For details, refer to the source code completionroutine in the attachment)
Code :--------------------------------------------------------------------------------
Void winapi apc_a (DWORD dwerror, DWORD cbtransferred, lpoverlapped LPO)
{
Ptempinfo. push_back ("execution completion routine of io_a ");
}
Void winapi apc_ B (DWORD dwerror, DWORD cbtransferred, lpoverlapped LPO)
{
Ptempinfo. push_back ("execution io_ B completion routine ");
}
Void winapi apc_c (DWORD dwerror, DWORD cbtransferred, lpoverlapped LPO)
{
Ptempinfo. push_back ("execution io_c completion routine ");
}
Void ccompletionroutinedlg: ontest ()
{
// Todo: add your control notification handler code here
Handle hfile_a, hfile_ B, hfile_c;
Overlapped ov_a = {0}, ov_ B = {0}, ov_c = {0 };
# Define c_size 1024*1024*32
String sztext_a = "Sample! ";
String sztext_ B = "sampel B! ";
String sztext_c;
Sztext_c.resize (c_size );
Memset (& (sztext_c [0]), 0x40, c_size );
Ptempinfo. Clear ();
Hfile_a = createfile ("a.txt", generic_write, 0, null ,/
Create_always, file_flag_overlapped, null );
Hfile_ B = createfile ("B .txt", generic_write, 0, null ,/
Create_always, file_flag_overlapped, null );
Hfile_c = createfile ("c.txt", generic_write, 0, null ,/
Create_always, file_flag_overlapped, null );
Writefileex (hfile_a, & (sztext_a [0]), sztext_a.length (), & ov_a, apc_a );
Ptempinfo. push_back ("START io_a and return immediately ");
Writefileex (hfile_ B, & (sztext_ B [0]), sztext_ B .length (), & ov_ B, apc_ B );
Ptempinfo. push_back ("START io_ B and return immediately ");
Writefileex (hfile_c, & (sztext_c [0]), sztext_c.size (), & ov_c, apc_c );
Ptempinfo. push_back ("START io_c and return immediately ");
Ptempinfo. push_back ("entering the variable wait state ");
Sleepex (1, true );
Ptempinfo. push_back ("End variable wait state ");
Ptempinfo. push_back ("entering the variable wait state ");
Sleepex (10000, true );
Ptempinfo. push_back ("End variable wait state ");
Closehandle (hfile_a );
Closehandle (hfile_ B );
Closehandle (hfile_c );
M_listbox.resetcontent ();
List <string >:: iterator P;
For (P = ptempinfo. Begin (); P! = Ptempinfo. End (); P ++)
{
M_listbox.addstring (p-> data ());
}
Deletefile ("a.txt ");
Deletefile ("B .txt ");
Deletefile ("c.txt ");
}
--------------------------------------------------------------------------------
The execution result is as follows (WINXP + SP2 + vc6.0 ):
4. Experience
Each time an IO operation ends, a complete message is generated. If this Io operation has a completed routine, it is added to the completion routine queue. Once the calling thread enters the variable wait state, it will execute the completion routine in the queue in sequence.
There is another problem in this example. If you place the software in the file directory of the system partition, it can be executed normally. If you place the software in another drive letter, the execution result will be different, it's strange.
Iv. Use the completed port (iocp)
Example 2: asynchronous I/O example using iocp
1. design objectives
Realize the asynchronous I/O implementation principle and process of the port.
2. Problem Analysis and Design
Note:
Each client interacts with a media transcoding queue. When I/O operations are completed, the completed packages will enter the "I/O completed package queue ". The thread in the thread queue that completes the port uses getqueuedcompletionstatus to check whether the complete package information exists in the "I/O completed package queue.
3. Detailed Design (the key code is as follows. For details, see the source code in the attachment)
Code :--------------------------------------------------------------------------------
Uint serverthread (lpvoid lpparameter)
{
......
While (true)
{
Getqueuedcompletionstatus (pmydlg-> hcompletionport, & cbtrans, & dwcompletionkey, & lpov, infinite );
If (dwcompletionkey =-1)
Break;
// Read MPs queue Information
// Response pipeline information (written)
}
Return 0;
}
Void cmydlg: onstart ()
{
// The created port
Hcompletionport = createiocompletionport (invalid_handle_value, null, 0, nmaxthread );
Cstring lppipename = "//. // pipe // namedpipe ";
For (uint I = 0; I <nmaxpipe; I ++)
{
// Create a named pipe
Pipeinst [I]. hpipe = createnamedpipe (lppipename, pipe_access_duplex | file_flag_overlapped ,/
Pipe_type_byte | pipe_readmode_byte | pipe_wait, nmaxpipe, 0, 0, infinite, null );
......
// Associate the named pipe with the completion port
Handle HRET = createiocompletionport (pipeinst [I]. hpipe, hcompletionport, I, nmaxthread );
......
// Wait for connection
Connectnamedpipe (pipeinst [I]. hpipe, & (pipeinst [I]. ov ));
}
// Create a thread
For (I = 0; I <nmaxthread; I ++)
{
Hthread [I] = afxbeginthread (serverthread, null, thread_priority_normal );
}
......
}
Void cmydlg: onstop ()
{
For (uint I = 0; I <nmaxthread; I ++)
{
// False I/O completion package used to wake up the thread
Postqueuedcompletionstatus (hcompletionport, 0,-1, null );
Closehandle (hthread [I]);
}
For (I = 0; I <nmaxpipe; I ++)
{
Disconnectnamedpipe (pipeinst [I]. hpipe );
Closehandle (pipeinst [I]. hpipe );
}
......
}