Библиотеки с многопоточной поддержкой

При проектировании пользовательских библиотек следует уделять самое пристальное внимание тому, чтобы избежать возникновения проблем, связанных с параллельным выполнением нескольких потоков, особенно в тех случаях, когда речь идет о сохранении информации о состоянии процессов. Одна из возможных стратегий демонстрируется в примере в главе 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){ } */

Наши рекомендации