// Create the I/O completion port.
hIOCompletionPort = CreateIoCompletionPort(hSourceFile,
NULL, // no existing I/O completion port
KEY, // key received in I/O completion packet
dwNumProcessors); // max worker threads
if (hIOCompletionPort == NULL)
{
fprintf(stderr,
"%s: Failed to create IO completion port (error %d)\n",
argv[0],
dwExitStatus = GetLastError());
goto EXIT;
}
// Initialize the file pointer for the overlapped structure
readPointer.LowPart = readPointer.HighPart = 0;
dwStartTime = GetTickCount();
// Start worker threads.
for (i=0; i<2*dwNumProcessors; i++)
{
hThread[i]=CreateThread(NULL,
0, // default stack size
(LPTHREAD_START_ROUTINE) readAndAnalyzeChunk,
(LPVOID) &fileSize,
0, // run immediately
&dwThreadId);
if (hThread[i] == NULL)
{
fprintf(stderr, "%s: Failed to create thread #%d (error %d)\n",
argv[0], i, dwExitStatus=GetLastError());
goto EXIT;
}
}
// Post the kickoff event.
PostQueuedCompletionStatus(hIOCompletionPort,
0,
KICKOFFKEY,
&kickoffOverlapped);
// Wait for a worker thread to terminate.
dwStatus = WaitForMultipleObjects(2*dwNumProcessors,
hThread,
FALSE,
INFINITE);
if (dwStatus == WAIT_FAILED)
{
fprintf(stderr, "%s: Wait failed (error %d)\n", argv[0],
dwExitStatus=GetLastError());
goto EXIT;
}
// Worker thread returned; send message to threads to exit.
for (i=0; i<2*dwNumProcessors-1; i++)
{
PostQueuedCompletionStatus(hIOCompletionPort,
0,
EXITKEY,
&dieOverlapped);
}
// Wait for threads to finish their work and terminate.
dwStatus = WaitForMultipleObjects(2*dwNumProcessors,
hThread,
TRUE,
INFINITE);
EXIT:
(void) _getch();
if (bInit)
DeleteCriticalSection(&critSec);
if (hThread[i])
CloseHandle(hThread[i]);
if (hIOCompletionPort)
CloseHandle(hIOCompletionPort);
if (hSourceFile)
CloseHandle(hSourceFile);
// Start asynchronous reads. Wait for read to complete, then do next read.
while(1){
bSuccess=GetQueuedCompletionStatus(hIOCompletionPort,
&dwNumBytes,
&dwKey,
&completedOverlapped,
INFINITE);
if (!bSuccess && (completedOverlapped==NULL))
{
fprintf(stderr, "GetQueuedCompletionStatus failed (error %d)\n", GetLastError());
exit(1);
}
if (dwKey==EXITKEY)
{
VirtualFree((LPVOID) chunk.buffer,
0,
MEM_RELEASE);
ExitThread(0);
}
if (!bSuccess)
{
fprintf(stderr, "GetQueuedCompletionStatus removed a bad I/O packet (error %d)\n", GetLastError());
// While you may want to fail here, this example proceeds.
}
if (dwKey != KICKOFFKEY)
{
// Analyze the data. The analysis for this example determines
// the number of pairs of consecutive bytes that are equal.
printf("Analyzing %d byte chunk\n", dwNumBytes);
completedChunk = (PCHUNK)completedOverlapped;
repeatCnt = 0;
for (i = 1; i < dwNumBytes; i++)
if ((((PBYTE)completedChunk->buffer)[i - 1] ^ ((PBYTE)completedChunk->buffer)[i]) == 0)
repeatCnt++;
printf("Repeat count was %d (thread #%d)\n", repeatCnt, dwThreadId);
// If the number of bytes returned is less than BUFFER_SIZE,
// that was the last read. Exit the thread.
if (dwNumBytes < BUFFER_SIZE)
ExitThread(0);
}
// Adjust OVERLAPPED structure for next read.
EnterCriticalSection(&critSec);
// Assume for this sample that we have a maximum of 16 processors.
#define MAX_THREADS 32
#define BUFFER_SIZE (64*1024)
DWORD dwNumProcessors;
// Handle for the source file to be analyzed
HANDLE hSourceFile=NULL;
// Handle to the I/O completion port
HANDLE hIOCompletionPort=NULL;
// Read position
ULARGE_INTEGER readPointer;
// Critical section
CRITICAL_SECTION critSec;
// ThreadProc function
DWORD WINAPI readAndAnalyzeChunk(LPVOID lpParam);
// Keys that control the behavior of the worker threads.
#define KICKOFFKEY 99 // Start reading and analyze the file
#define KEY 1 // Read the next chunk and analyze it
#define EXITKEY 86 // Exit (the file has been read and analyzed)
The sample code demonstrates a multi-threaded application designed to read and analyze a large file. Asynchronous I/O is used to read a chunk of the file at a time by one of a pool of worker threads. The completed I/O is posted at an I/O completion port. A worker thread picks up the completed I/O, analyzes it, reports the results, and starts another asynchronous read operation. The process continues until the whole file has been read.
The number of threads is based on the number of processors in the host. Specifically, it is twice that number. See I/O Completion Ports for more details.
The contrived "analysis" is a count of the number of identical consecutive byte pairs in each chunk.
The file to be analyzed is specified on the command line. The sample is most meaningful if the specified file that is 1MB or larger in size.
The code consists of a single thread running the main function and several identical worker threads running the function named readAndAnalyzeChunk. These functions are described following.
main function
Determines the number of processors, which is related to the concurrency value for the I/O completion port.
Opens the source file to be analyzed for unbuffered asynchronous I/O.
Determines the size of the file.
Initializes a read pointer that will be accessed by worker threads within a critical section.
Creates a single I/O completion port at which completed reads post.
Creates a pool of worker threads (twice the number of processors on the system) and starts each one running readChunkAndAnalyze.
Posts a single "kickoff" I/O completion at the I/O completion port, which causes the worker threads to begin.
Waits for any one of the worker threads to exit, signifying that it has analyzed the data that was read from the last chunk of the file.
Posts a set of "exit" I/O completions at the I/O completion port. This this indicates that the threads should exit after completing any remaining analysis.
Waits for all threads to terminate.
Displays some statistics and exits.
readAndAnalyzeChunk function
Waits on a completed I/O at the I/O completion port upon creation.
One thread begins reading the file when the "kickoff" key is posted by the main program.
Analyzes the data block returned by entering a critical section, retrieving the read pointer, updating the read pointer for the next thread, leave the critical section.
Exits when the "exit" key is posted.