Библиотеки с многопоточной поддержкой
При проектировании пользовательских библиотек следует уделять самое пристальное внимание тому, чтобы избежать возникновения проблем, связанных с параллельным выполнением нескольких потоков, особенно в тех случаях, когда речь идет о сохранении информации о состоянии процессов. Одна из возможных стратегий демонстрируется в примере в главе 12 (программа 12.4), где библиотека DLL для сохранения информации о состоянии использует отдельный параметр.
Еще один пример в главе 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);
}
/* Перенаправить стандартный вывод для вывода списка файлов. */
Startup.dwFlags = STARTF_USESTDHANDLES;
Startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
/* Выполняются все рабочие потоки. Ожидать их завершения. */
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) { /* Шаблон найден. */
if (argc > 3) {
/* Вывести имя файла, если имеется несколько файлов. */
_tprintf(_T("\n**Результаты поиска – файл: %s\n"), gArg[iThrd].targv [2]);
fflush(stdout);
}
/* Использовать программу "cat" для перечисления результирующих файлов. */
_stprintf(CmdLine, _T("%s%s"), _Т("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]);
/* Скорректировать массивы потоков и имен файлов. */
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--;
}
}
/* Прототип функции контекстного поиска:
static DWORD WINAPI ThGrep(PGR_ARGS pArgs){ } */