Example: Multithreaded Pattern Searching
Program 6-1, grepMP, used processes to search multiple files simultaneously. Chapter 14. In this example, the program is managing concurrent I/O to multiple files, and the main thread, or any other thread, can perform additional processing before waiting for I/O completion. In the author's opinion, threads are a much simpler method of achieving asynchronous I/O, and Chapter 14 compares the methods, allowing readers to form their own opinions. We will see, however, that asynchronous I/O, combined with I/O completion ports, is useful and often necessary when the number of threads is large.grepMT, for the purposes of illustration, differs in another way from grepMP. Here, WaitForMultipleObjects waits for a single thread to terminate rather than waiting for all the threads. The appropriate output is displayed before waiting for another thread to complete. The completion order will, in most cases, vary from one run to the next. It is easy to modify the program to display the results in the order of the command line arguments; just imitate grepMP.Finally, notice that there is a limit of 64 threads due to the value of MAXIMUM_WAIT_OBJECTS, which limits the number of handles in the WaitForMultipleObjects call. If more threads are required, create the appropriate logic to loop on either WaitForSingleObject or WaitForMultipleObjects.Caution:
grepMT performs asynchronous I/O in the sense that separate threads are concurrently, and synchronously, reading different files with read operations that block until the read is complete. You can also concurrently read from the same file if you have distinct handles on the file (typically, one per thread). These handles should be generated by CreateFile rather than DuplicateHandle. Chapter 14 describes asynchronous I/O, with and without user threads, and an example on the book's Web site (atouMT, described in Chapter 14) has several threads performing I/O to the same file.
Program 7-1. grepMT: Multithreaded Pattern Searching
/* Chapter 7. grepMT. */
/* Parallel grep -- multiple thread version. */
#include "EvryThng.h"
typedef struct { /* grep thread's data structure. */
int argc;
TCHAR targv [4] [MAX_PATH];
} GREP_THREAD_ARG;
typedef GREP_THREAD_ARG *PGR_ARGS;
static DWORD WINAPI ThGrep (PGR_ARGS pArgs);
int _tmain (int argc, LPTSTR argv [])
{
GREP_THREAD_ARG * gArg;
HANDLE * tHandle;
DWORD ThdIdxP, ThId, ExitCode;
TCHAR CmdLine [MAX_COMMAND_LINE];
int iThrd, ThdCnt;
STARTUPINFO StartUp;
PROCESS_INFORMATION ProcessInfo;
GetStartupInfo (&StartUp);
/* Boss thread: create separate "grep" thread for each file. */
tHandle = malloc ((argc - 2) * sizeof (HANDLE));
gArg = malloc ((argc - 2) * sizeof (GREP_THREAD_ARG));
for (iThrd = 0; iThrd < argc - 2; iThrd++) {
_tcscpy (gArg [iThrd].targv [1], argv [1]); /* Pattern. */
_tcscpy (gArg [iThrd].targv [2], argv [iThrd + 2]);
GetTempFileName /* Temp file name. */
(".", "Gre", 0, gArg [iThrd].targv [3]);
gArg [iThrd].argc = 4;
/* Create a worker thread to execute the command line. */
tHandle [iThrd] = (HANDLE)_beginthreadex (
NULL, 0, ThGrep, &gArg [iThrd], 0, &ThId);
}
/* Redirect std output for file listing process. */
StartUp.dwFlags = STARTF_USESTDHANDLES;
StartUp.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
/* Worker threads are all running. Wait for them to complete. */
ThdCnt = argc - 2;
while (ThdCnt > 0) {
ThdIdxP = WaitForMultipleObjects (
ThdCnt, tHandle, FALSE, INFINITE);
iThrd = (int) ThdIdxP - (int) WAIT_OBJECT_0;
GetExitCodeThread (tHandle [iThrd], &ExitCode);
CloseHandle (tHandle [iThrd]);
if (ExitCode == 0) { /* Pattern found. */
if (argc > 3) {
/* Print file name if more than one. */
_tprintf (_T ("\n**Search results - file: %s\n"),
gArg [iThrd].targv [2]);
fflush (stdout);
}
/* Use the "cat" program to list the result files. */
_stprintf (CmdLine, _T ("%s%s"), _T ("cat "),
gArg [iThrd].targv [3]);
CreateProcess (NULL, CmdLine, NULL, NULL,
TRUE, 0, NULL, NULL, &StartUp, &ProcessInfo);
WaitForSingleObject (ProcessInfo.hProcess, INFINITE);
CloseHandle (ProcessInfo.hProcess);
CloseHandle (ProcessInfo.hThread);
}
DeleteFile (gArg [iThrd].targv [3]);
/* Adjust thread and file name arrays. */
tHandle [iThrd] = tHandle [ThdCnt - 1];
_tcscpy (gArg [iThrd].targv [3], gArg [ThdCnt - 1].targv [3]);
_tcscpy (gArg [iThrd].targv [2], gArg [ThdCnt - 1].targv [2]);
ThdCnt--;
}
}
/* The form of the grep thread function code is:
static DWORD WINAPI ThGrep (PGR_ARGS pArgs)
{
} */