Еще один пример в главе 12 (программа 12.5) иллюстрирует альтернативный подход, в котором применяется функция DllMain и TLS, описанные далее в настоящей главе.
Пример: многопоточный поиск контекста
В программе 6.1 (grepMP) для выполнения одновременного поиска текстового шаблона в нескольких файлах использовались процессы. Программа 7.1 (grepMT), которая включает исходный код функции поиска текстового шаблона grep, обеспечивает выполнение поиска несколькими потоками в рамках одного процесса. Код функции поиска основан на вызовах функций файлового ввода/вывода библиотеки С. Основная программа аналогична той, которая предлагалась в варианте реализации, основанном на использовании процессов.
Этот пример также показывает, что применение потоков позволяет выполнять асинхронные операции ввода/вывода даже без привлечения специально для этого предназначенных методов, описанных в главе 14. В данном примере параллельным вводом/выводом с участием нескольких файлов управляет программа, в то время как основной или любого другого потока предоставляется возможность в ожидании завершения ввода/вывода выполнять дополнительную обработку. По мнению автора, способ реализации асинхронного ввода/вывода, обеспечиваемый потоками, является более простым, а сравнительный анализ эффективности различных методов, представленный в главе 14, поможет вам выработать собственное мнение на этот счет.
Мы увидим, однако, что в сочетании с портами завершения ввода/вывода операции асинхронного ввода/вывода становятся очень полезным, а часто и необходимым средством в тех случаях, когда количество потоков очень велико.
В иллюстративных целях в программу grepMT введено дополнительное отличие по сравнению с программой grepMP. В данном случае функция WaiForMultipleObjects ожидает завершения не всех потоков, а только одного. Соответствующая информация выводится без ожидания завершения других потоков. В большинстве случае порядок завершения потоков будет меняться от одного запуска программы к другому. Программу легко видоизменить таким образом, чтобы результаты отображались в порядке указания аргументов в командной строке; для этого будет достаточно сымитировать программу grepMP.
Наконец, обратите внимание на ограничение в 64 потока, обусловленное значением константы MAXIMUM_WAIT_OBJECTS, которая ограничивает количество дескрипторов при вызове функции WaitForMultipleObjects. Если у вас возникнет необходимость в большем количестве потоков, организуйте для функций WaitForSingleObjects или WaitForMultipleObjects соответствующий цикл.
Предостережение
Программа grepMP осуществляет асинхронный ввод/вывод в том смысле, что отдельные потоки выполняют параллельное синхронное чтение различных файлов, которые блокируются до момента завершения операции чтения. Можно также организовать параллельное чтение одного и того же файла, если у него имеются различные дескрипторы (обычно, по одному дескриптору для каждого потока). Эти дескрипторы должны быть сгенерированы функцией CreateFile, а не функцией DuplicateHandle. В главе 14 описывается асинхронный ввод/вывод, осуществляемый как с использованием, так и без использования пользовательских потоков, а в примере, доступном на Web-сайте (программа atouMT, описанная в главе 14), операции ввода/вывода выполняются с использованием нескольких потоков по отношению к одному и тому же файлу.
Программа 7.1. grepMT: многопоточный поиск текстового шаблона/* Глава 7. grepMT. */
/* Параллельный поиск текстового шаблона — версия, использующая несколько потоков. */
#include "EvryThng.h"
typedef struct { /* Структура данных потока поиска. */
int argc;
TCHAR targv[4][МАХ_РАТН];
} 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);
/* Основной поток: создает отдельные потоки поиска на основе функции "grep" для каждого файла. */
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 /* Имя временного файла. */
(".", "Gre", 0, gArg[iThrd].targv[3]);
gArg[iThrd].argc = 4;
/* Создать рабочий поток для выполнения командной строки. */
tHandle[iThrd] = (HANDLE)_beginthreadex(NULL, 0, ThGrep, &gArg[iThrd], 0, &ThId);