Издание четвертое windows ® для профессионалов создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows


Г ЛАВА 10Полезные средства для синхронизации потоковWaitForMultExp.cpp



Pdf просмотр
страница31/68
Дата28.11.2016
Размер3.57 Mb.
Просмотров12467
Скачиваний0
1   ...   27   28   29   30   31   32   33   34   ...   68
Г ЛАВА 10
Полезные средства для синхронизации потоков
WaitForMultExp.cpp
/******************************************************************************
Модуль: Автор Copyright (c) 2000, Джеффри Рихтер (Jeffrey Richter)
******************************************************************************/
#include "..\CmnHdr.h"
/* см. приложение А */
#include
#include
#include "WaitForMultExp.h"
///////////////////////////////////////////////////////////////////////////////
// внутренняя структура данных, которая представляет одно выражение используется для того, чтобы сообщать OR потокам на каких объектах они должны ждать typedef struct {
PHANDLE m_phExpObjects; // указывает на набор описателей m_nExpObjects; // количество описателей EXPRESSION, *PEXPRESSION;
///////////////////////////////////////////////////////////////////////////////
// функция OR потока WINAPI WFME_ThreadExpression(PVOID pvParam) {
// Эта функция просто ждет, когда выражение станет истинным Ее поток ждет в "тревожном" состоянии, чтобы его ожидание можно было принудительно прервать, поместив в его APC очередь APC вызов pExpression = (PEXPRESSION) pvParam;
return(WaitForMultipleObjectsEx(
pExpression >m_nExpObjects, pExpression >m_phExpObjects,
TRUE, INFINITE, TRUE));
}
///////////////////////////////////////////////////////////////////////////////
// это APC функция обратного вызова WINAPI WFME_ExpressionAPC(ULONG_PTR dwData) {
// в тело функции преднамеренно не включено никаких операторов эта функция ждет по нескольким булевым выражениям WINAPI WaitForMultipleExpressions(DWORD nExpObjects,
CONST HANDLE* phExpObjects, DWORD dwMilliseconds) {
Рис. 10-4.
Программа-пример WaitForMultExp
см. след. стр.

282
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Рис. 10-4.
продолжение
// создаем временный массив, потому что нам придется модифицировать передаваемый массив и добавить в его конец описатель для семафора hsemOnlyOne
PHANDLE phExpObjectsTemp = (PHANDLE)
_alloca(sizeof(HANDLE) * (nExpObjects + 1));
CopyMemory(phExpObjectsTemp, phExpObjects, sizeof(HANDLE) * nExpObjects);
phExpObjectsTemp[nExpObjects] = NULL; // ставим сюда часового этот семафор гарантирует, что только одно выражение станет истинным hsemOnlyOne = CreateSemaphore(NULL, 1, 1, NULL);
// информация о выражении Expression[MAXIMUM_WAIT_OBJECTS];
DWORD dwExpNum
= 0; // номер текущего выражения dwNumExps = 0; // общее количество выражений dwObjBegin = 0; // первый индекс набора dwObjCur = 0; // текущий индекс объекта в наборе dwThreadId, dwWaitRet = 0;
// массив описателей потоков ahThreads[MAXIMUM_WAIT_OBJECTS];
// разбираем список описателей вызывающих потоков, инициализируя структуру для каждого выражения и добавляя к нему hsemOnlyOne while ((dwWaitRet != WAIT_FAILED) && (dwObjCur <= nExpObjects)) {
// пока ошибок нет и описатели объектов находятся в списке вызывающего...
// находим следующее выражение (OR выражения разделяются NULL описателями)
while (phExpObjectsTemp[dwObjCur] != NULL)
dwObjCur++;
// инициализируем структуру Expression, на которой ждет OR поток phExpObjectsTemp[dwObjCur] = hsemOnlyOne;
Expression[dwNumExps].m_phExpObjects = &phExpObjectsTemp[dwObjBegin];
Expression[dwNumExps].m_nExpObjects
= dwObjCur dwObjBegin + 1;
if (Expression[dwNumExps].m_nExpObjects > MAXIMUM_WAIT_OBJECTS) {
// ошибка слишком много описателей водном выражении dwWaitRet = WAIT_FAILED;
SetLastError(ERROR_SECRET_TOO_LONG);
}
// переходим к следующему выражению dwObjBegin = ++dwObjCur;
if (++dwNumExps == MAXIMUM_WAIT_OBJECTS) {
// ошибка слишком много выражений dwWaitRet = WAIT_FAILED;

283
Г ЛАВА 10
Полезные средства для синхронизации потоков
Рис. 10-4.
продолжение
SetLastError(ERROR_TOO_MANY_SECRETS);
}
}
if (dwWaitRet != WAIT_FAILED) {
// при разборе списка описателей ошибки не обнаружены порождаем поток для каждого выражения for (dwExpNum = 0; dwExpNum < dwNumExps; dwExpNum++) {
ahThreads[dwExpNum] = chBEGINTHREADEX(NULL,
1, // нам нужен лишь небольшой стек
WFME_ThreadExpression, &Expression[dwExpNum],
0, &dwThreadId);
}
// ждем, когда выражение станет TRUE или когда истечет срок ожидания dwWaitRet = WaitForMultipleObjects(dwExpNum, ahThreads,
FALSE, dwMilliseconds);
if (WAIT_TIMEOUT == dwWaitRet) {
// срок ожидания истек; выясняем, не стало ли какое нибудь выражение
// истинным, проверяя состояние семафора hsemOnlyOne dwWaitRet = WaitForSingleObject(hsemOnlyOne, 0);
if (WAIT_TIMEOUT == dwWaitRet) {
// если семафор не был переведен в свободное состояние,
// какое то выражение дало TRUE; надо выяснить — какое dwWaitRet = WaitForMultipleObjects(dwExpNum,
ahThreads, FALSE, INFINITE);
} else {
// ни одно выражение не стало TRUE,
// и WaitForSingleObject просто отдала нам семафор dwWaitRet = WAIT_TIMEOUT;
}
}
// прерываем ожидание всех потоков (ждущих свои выражения),
// чтобы они могли корректно завершиться for (dwExpNum = 0; dwExpNum < dwNumExps; dwExpNum++) {
if ((WAIT_TIMEOUT == dwWaitRet) ||
(dwExpNum != (dwWaitRet
WAIT_OBJECT_0))) {
QueueUserAPC(WFME_ExpressionAPC, ahThreads[dwExpNum], 0);
}
}
см. след. стр.

284
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Рис. 10-4.
продолжение
#ifdef _DEBUG
// при отладочной сборке ждем завершения всех потоков выражений а при окончательной сборке считаем, что все работает, как надои не заставляем этот поток ждать их завершения, ahThreads, TRUE, INFINITE);
#endif
// закрываем свои описатели всех потоков выражений for (dwExpNum = 0; dwExpNum < dwNumExps; dwExpNum++) {
CloseHandle(ahThreads[dwExpNum]);
}
} // при разборе произошла ошибка
CloseHandle(hsemOnlyOne);
return(dwWaitRet);
}
//////////////////////////////// Конец файла //////////////////////////////////
WaitForMultExp.h
/******************************************************************************
Модуль: Автор Copyright (c) 2000, Джеффри Рихтер (Jeffrey Richter)
******************************************************************************/
#pragma once
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI WaitForMultipleExpressions(DWORD nExpObjects,
CONST HANDLE* phExpObjects, DWORD dwMilliseconds);
//////////////////////////////// Конец файла //////////////////////////////////
WfMETest.cpp
/******************************************************************************
Модуль: Автор Copyright (c) 2000, Джеффри Рихтер (Jeffrey Richter)
******************************************************************************/
#include "..\CmnHdr.h"
/* см. приложение А */
#include
#include
#include
#include "resource.h"
#include "WaitForMultExp.h"
///////////////////////////////////////////////////////////////////////////////
см. след. стр.

285
Г ЛАВА 10
Полезные средства для синхронизации потоков
Рис. 10-4.
продолжение
// g_ahObjs содержит список описателей объектов ядра "событие на которые ссылается булево выражение MAX_KERNEL_OBJS 1000
HANDLE g_ahObjs[MAX_KERNEL_OBJS];
// ahExpObjs содержит все выражения каждое выражение состоит из набора описателей объектов ядра и становится TRUE, когда все эти объекты одновременно переходят в свободное состояние чтобы отделить OR выражения друг от друга, используем NULL описатель один и тот же описатель может встречаться в AND выражении только разно может повторяться в OR выражениях выражение может содержать максимум 64 набора, а каждый набор —
// не более 63 описателей плюс NULL описатель (разделитель наборов MAX_EXPRESSION_SIZE ((64 * 63) + 63)
// m_nExpObjects — число элементов, задействованных в массиве ahExpObjects typedef struct {
HWND m_hwnd;
// куда посылать результаты m_dwMilliseconds;
// сколько ждать m_nExpObjects;
// количество элементов в списке объектов m_ahExpObjs[MAX_EXPRESSION_SIZE]; // список объектов AWFME, *PAWFME;
AWFME g_awfme;
// это сообщение асинхронно посылается потоку пользовательского интерфейса когда какое то выражение становится истинным или заканчивается заданное время ожидания WM_WAITEND
(WM_USER + 101)
///////////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {
chSETDLGICONS(hwnd, IDI_WFMETEXT);
// инициализируем элементы управления в диалоговом окне, 4, FALSE);
SetDlgItemInt(hwnd,
IDC_TIMEOUT, 30000, FALSE);
SetDlgItemText(hwnd, IDC_EXPRESSION,
_T("1 2 | 2 3 4 | 1 4"));
// устанавливаем размер колонки в окне многоколоночного списка, IDC_OBJLIST),
LOWORD(GetDialogBaseUnits()) * 4);
return(TRUE);
}
см. след. стр.

286
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Рис. 10-4.
продолжение
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI AsyncWaitForMultipleExpressions(PVOID pvParam) {
PAWFME pawfme = (PAWFME) pvParam;
DWORD dw = WaitForMultipleExpressions(pawfme >m_nExpObjects,
pawfme >m_ahExpObjs, pawfme >m_dwMilliseconds);
PostMessage(pawfme >m_hwnd, WM_WAITEND, dw, 0);
return(0);
}
///////////////////////////////////////////////////////////////////////////////
LRESULT Dlg_OnWaitEnd(HWND hwnd, WPARAM wParam, LPARAM lParam) {
// закрываем все описатели объектов ядра "событие (int n = 0; g_ahObjs[n] != NULL; n++)
CloseHandle(g_ahObjs[n]);
// сообщаем пользователю результат выполнения теста if (wParam == WAIT_TIMEOUT)
SetDlgItemText(hwnd, IDC_RESULT, __TEXT("Timeout"));
else
SetDlgItemInt(hwnd, IDC_RESULT, (DWORD) wParam
WAIT_OBJECT_0, FALSE);
// даем возможность изменить значения и повторно выполнить тест, IDC_NUMOBJS),
TRUE);
EnableWindow(GetDlgItem(hwnd, IDC_TIMEOUT),
TRUE);
EnableWindow(GetDlgItem(hwnd, IDC_EXPRESSION), TRUE);
EnableWindow(GetDlgItem(hwnd, IDOK),
TRUE);
SetFocus(GetDlgItem(hwnd, IDC_EXPRESSION));
return(0);
}
///////////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
// получаем пользовательские настройки из элементов управления диалогового окна szExpression[100];
ComboBox_GetText(GetDlgItem(hwnd, IDC_EXPRESSION), szExpression,
sizeof(szExpression) / sizeof(szExpression[0]));
int nObjects = GetDlgItemInt(hwnd, IDC_NUMOBJS, NULL, FALSE);
switch (id) {
case IDCANCEL:
EndDialog(hwnd, id);
break;

287
Г ЛАВА 10
Полезные средства для синхронизации потоков
Рис. 10-4.
продолжение
case IDC_OBJLIST:
switch (codeNotify) {
case LBN_SELCHANGE:
// состояние элемента изменено, сбрасываем все элементы и устанавливаем только выбранные for (int n = 0; n < nObjects; n++)
ResetEvent(g_ahObjs[n]);
for (n = 0; n < nObjects; n++) {
if (ListBox_GetSel(GetDlgItem(hwnd, IDC_OBJLIST), n))
SetEvent(g_ahObjs[n]);
}
break;
}
break;
case IDOK:
// запрещаем изменение значений в процессе выполнения теста
SetFocus(GetDlgItem(hwnd, IDC_OBJLIST));
EnableWindow(GetDlgItem(hwnd, IDC_NUMOBJS),
FALSE);
EnableWindow(GetDlgItem(hwnd, IDC_TIMEOUT),
FALSE);
EnableWindow(GetDlgItem(hwnd, IDC_EXPRESSION), FALSE);
EnableWindow(GetDlgItem(hwnd, IDOK),
FALSE);
// уведомляем пользователя, что тест выполняется
SetDlgItemText(hwnd, IDC_RESULT, TEXT("Waiting..."));
// создаем все необходимые объекты ядра
ZeroMemory(g_ahObjs, sizeof(g_ahObjs));
g_awfme.m_nExpObjects = 0;
ZeroMemory(g_awfme.m_ahExpObjs, sizeof(g_awfme.m_ahExpObjs));
g_awfme.m_hwnd = hwnd;
g_awfme.m_dwMilliseconds = GetDlgItemInt(hwnd, IDC_TIMEOUT, NULL, FALSE);
ListBox_ResetContent(GetDlgItem(hwnd, IDC_OBJLIST));
for (int n = 0; n < nObjects; n++) {
TCHAR szBuf[20];
g_ahObjs[n] = CreateEvent(NULL, FALSE, FALSE, NULL);
wsprintf(szBuf, TEXT("
%d"), n + 1);
ListBox_AddString(GetDlgItem(hwnd, IDC_OBJLIST),
&szBuf[lstrlen(szBuf)
3]);
}
PTSTR p = _tcstok(szExpression, TEXT(" "));
while (p != NULL) {
g_awfme.m_ahExpObjs[g_awfme.m_nExpObjects++] =
(*p == TEXT('|')) ? NULL : g_ahObjs[_ttoi(p)
1];
p = _tcstok(NULL, TEXT(" "));
}
см. след. стр.

288
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Рис. 10-4.
продолжение
DWORD dwThreadId;
CloseHandle(chBEGINTHREADEX(NULL, 0,
AsyncWaitForMultipleExpressions, &g_awfme,
0, &dwThreadId));
break;
}
}
///////////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
case WM_WAITEND:
return(Dlg_OnWaitEnd(hwnd, wParam, lParam));
}
return(FALSE);
}
///////////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) {
DialogBox(hinstExe, MAKEINTRESOURCE(IDD_TESTW4ME), NULL, Dlg_Proc);
return(0);
}
//////////////////////////////// Конец файла //////////////////////////////////

289
Г ЛАВА 1
Пулы потоков
В
главе 8 мы обсудили синхронизацию потоков без перехода в режим ядра. Замечательная особенность такой синхронизации — высокое быстродействие. И если Вы озабочены быстродействием потока, сначала подумайте, нельзя ли обойтись синхронизацией в пользовательском режиме.
Вы уже знаете, что создание многопоточных приложений — дело трудное. Вас подстерегают две серьезные проблемы управление созданием и уничтожением потоков и синхронизация их доступа к ресурсам. Для решения второй проблемы в предусмотрено множество синхронизирующих примитивов события, семафоры, мьютексы, критические секции и др. Все они довольно простыв использовании. Но если бы система автоматически охраняла разделяемые ресурсы, вот тогда создавать многопоточные приложения было бы по настоящему легко. Увы, операционной системе Windows до этого еще далеко.
Проблему того, как управлять созданием и уничтожением потоков, каждый решает по своему. За прошедшие годы я создал несколько реализаций пулов потоков, рассчитанных на определенные сценарии. Однако в Windows 2000 появился ряд новых функций для операций с пулами потоков эти функции упрощают создание, уничтожение и общий контроль за потоками. Конечно, встроенные в них механизмы носят общий характер и не годятся на все случаи жизни, но зачастую их вполне достаточно, и они позволяют экономить массу времени при разработке многопоточного при ложения.
Эти функции дают возможность вызывать другие функции асинхронно, через определенные промежутки времени, при освобождении отдельных объектов ядра или при завершении запросов на асинхронный ввод вывод.
Пул подразделяется на четыре компонента, которые описываются в таблице 11 Компонент поддержки
таймера
ожидания
ввода-вывода
других операций
Начальное
Всегда 1 1
0 число потоков
Когда поток
При вызове
Один поток
В системе применяются эвристические создается первой функции для каждых методы, нона создание потока влияют таймера пула зарегист следующие факторы:
потоков рированных
í
после добавления потока прошло объектов определенное время
í
установлен флаг
WT_EXECUTELONGFUNCTION
í
число элементов в очереди превышает пороговое значение
Таблица 11-1.
Компоненты поддержки пула потоков
см. след. стр.

290
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
продолжение
Компонент поддержки
таймера
ожидания
ввода-вывода
других операций
Когда поток
При завершении
При отсутст
При отсутствии
При простое потока разрушается процесса вии зарегист у потока текущих в течение определен рированных запросов на ного порогового объектов ввод выводи времени (около ожидания простоев течение минуты)
определенного порогового времени
(около минуты)
Как поток ждет
В «тревожном»
WaitFor
В «тревожном»
GetQueued
состоянии
Multiple
состоянии
CompletionStatus
ObjectsEx
Когда поток
При освобожде
При
При посылке в
При поступлении пробуждается нии ожидаемого освобождении очередь APC вызова запроса о статусе таймера, кото объекта ядра или завершении завершения или рый посылает запроса на о завершении ввода в очередь ввод вывод вывода (порт завер
APC вызов шения требует, чтобы число потоков не превышало число процессоров более чем в 2 раза)
При инициализации процесса никаких издержек, связанных с перечисленными в таблице компонентами поддержки, не возникает. Однако, как только вызывается одна из функций пула потоков, для процесса создается набор этих компонентов, и некоторые из них сохраняются до его завершения. Как видите, издержки от применения этих функций отнюдь немалые частью Вашего процесса становится целый набор потоков и внутренних структур данных. Так что, прежде чем пользоваться ими, тщательно взвесьте все за и «против».
О’кэй, теперь, когда я Вас предупредил, посмотрим, как все это работает.
Сценарий 1: асинхронный вызов функций
Допустим, у Вас есть серверный процесс с основным потоком, который ждет клиентский запрос. Получив его, он порождает отдельный поток для обработки этого запроса. Тем самым основной поток освобождается для приема следующего клиентского запроса. Такой сценарий типичен в клиент серверных приложениях. Хотя они так то незатейлив, при желании его можно реализовать с использованием новых функций пула потоков.
Получая клиентский запрос, основной поток вызывает QueueUserWorkItem(
PTHREAD_START_ROUTINE pfnCallback,
PVOID pvContext,
ULONG Эта функция помещает рабочий элемент (work item) в очередь потока в пуле и тут же возвращает управление. Рабочий элемент — это просто вызов функции (на которую ссылается параметр
pfnCallback), принимающей единственный параметр,
pvContext. В конечном счете какой то поток из пула займется обработкой этого эле

291
Г ЛАВА 11
Пулы потоков мента, в результате чего будет вызвана Ваша функция. У этой функции обратного вызова, за реализацию которой отвечаете Вы, должен быть следующий прототип WINAPI WorkItemFunc(PVOID Несмотря на то что тип возвращаемого значения определен как DWORD, на самом деле оно игнорируется.
Обратите внимание, что Вы сами никогда не вызываете
CreateThread. Она вызывается из пула потоков, автоматически создаваемого для Вашего процесса, а к функции обращается один из потоков этого пула. Кроме того, данный поток не уничтожается сразу после обработки клиентского запроса, а возвращается в пул,
оставаясь готовым к обработке любых других элементов, помещаемых в очередь. Ваше приложение может стать гораздо эффективнее, так как Вам больше не придется создавать и уничтожать потоки для каждого клиентского запроса. А поскольку потоки связаны с определенным портом завершения, количество одновременно работающих потоков не может превышать число процессоров более чем в 2 раза. За счет этого переключения контекста происходят реже.
Многое в пуле потоков происходит скрытно от разработчика
QueueUserWorkItem
проверяет число потоков, включенных в сферу ответственности компонента поддержки других операций (не относящихся к вводу выводу, ив зависимости от текущей нагрузки (количества рабочих элементов в очереди) может передать ему другие потоки. После этого
QueueUserWorkItem выполняет операции, эквивалентные вызову
PostQueuedCompletionStatus, пересылая информацию о рабочем элементе в порт завершения ввода вывода. В конечном счете поток, ждущий на этом объекте, извлекает
Ваше сообщение (вызовом
GetQueuedCompletionStatus) и обращается к Вашей функции. После того как она возвращает управление, поток вновь вызывает
GetQueued
CompletionStatus, ожидая появления следующего рабочего элемента.
Пул рассчитан на частую обработку асинхронного ввода вывода — всякий раз,
когда поток помещает в очередь запрос на ввод вывод к драйверу устройства. Пока драйвер выполняет его, поток, поставивший запрос в очередь, не блокируется и может заниматься другой работой. Асинхронный ввод вывод — ключ к созданию высокоэффективных, масштабируемых приложений, так как позволяет одному потоку обрабатывать запросы от множества клиентов по мере их поступления ему не приходится обрабатывать их последовательно или останавливаться, ожидая завершения ввода вывода.
Но Windows накладывает одно ограничение на запросы асинхронного ввода вывода если поток, послав такой запрос драйверу устройства, завершается, данный запрос теряется и никакие потоки о его судьбе не уведомляются. В хорошо продуманном пуле, число потоков увеличивается и уменьшается в зависимости от потребностей его клиентов. Поэтому, если поток посылает запроси уничтожается из за сокращения пула, то уничтожается и этот запрос. Как правило, это не совсем то, что хотелось бы, и здесь нужно найти какое то решение.
Если Вы хотите поместить в очередь рабочий элемент, который выдает запрос на асинхронный ввод вывод, тоне сможете передать этот элемент компоненту поддержки других операций в пуле потоков. Его примет лишь компонент поддержки ввода вывода. Последний включает набор потоков, которые не завершаются, пока есть хотя бы один запрос на ввод вывод поэтому для выполнения кода, выдающего запросы на асинхронный ввод вывод, Вы должны пользоваться только этими потоками.
Чтобы передать рабочий элемент компоненту поддержки ввода вывода, Вы можете по прежнему пользоваться функцией
QueueUserWorkItem, нов параметре dwFlags

292
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
следует указать флаг WT_EXECUTEINIOTHREAD. А обычно Выбудете указывать в этом параметре флаг WT_EXECUTEDEFAULT (0) — он заставляет систему передать рабочий элемент компоненту поддержки других операций (несвязанных с вводом выводом).
В Windows есть функции (вроде
RegNotifyChangeKeyValue), которые асинхронно выполняют операции, не относящиеся к вводу выводу. Они также требуют, чтобы вызывающий поток не завершался преждевременно. С этой целью Вы можете использовать флаг WT_EXECUTEINPERSISTENTTHREAD, который заставляет поток таймера выполнять поставленную в очередь функцию обратного вызова для рабочего элемента. Так как этот компонент существует постоянно, асинхронная операция в конечном счете обязательно будет выполнена. Вы должны позаботиться о том, чтобы функция обратного вызова выполнялась быстро и не блокировала работу компонента поддержки таймера.
Хорошо продуманный пул должен также обеспечивать максимальную готовность потоков к обработке запросов. Если в пуле четыре потока, а в очереди сто рабочих элементов, то единовременно можно обработать только четыре элемента. Это не проблема, если на обработку каждого элемента уходит лишь несколько миллисекунд,
но вином случае программа не сумеет своевременно обслуживать запросы.
Естественно, система не настолько умна, чтобы предвидеть, чем будет заниматься функция Вашего рабочего элемента, но если Вам заранее известно, что на это уйдет длительное время, вызовите
QueueUserWorkItem с флагом WT_EXECUTELONGFUNC
TION — он заставит пул создать новый поток, если остальные потоки будут в это время заняты. Так, добавив в очередь 10 000 рабочих элементов (с флагом WT_EXECUTE
LONGFUNCTION), Вы получите 10 000 новых потоков в пуле. Чтобы избежать этого,
делайте перерывы между вызовами
QueueUserWorkItem, и тогда часть потоков успеет завершиться до порождения новых.
Ограничение на количество потоков в пуле накладывать нельзя, иначе может возникать взаимная блокировка потоков. Представьте очередь из 10 000 элементов, заблокированных ми ждущих его освобождения. Установив предел в 10 000, Вы запретите выполнение 10 001 го потока, ив результате целых 10 000 потоков останутся навечно заблокированными.
Используя функции пула, будьте осторожны, чтобы не доводить дело до тупиковых ситуаций. Особую осторожность проявляйте, если функция Вашего рабочего элемента использует критические секции, семафоры, мьютексы и др. — это увеличивает вероятность взаимной блокировки. Вы должны всегда точно знать, поток какого компонента пула выполняет Ваш код. Также будьте внимательны, если функция рабочего элемента содержится в DLL, которая может быть динамически выгружена из памяти.
Поток, вызывающий функцию из выгруженной DLL, приведет к нарушению доступа.
Чтобы предотвратить выгрузку DLL при наличии рабочих элементов в очереди, создайте контрольный счетчик для таких элементов его значение должно увеличиваться перед вызовом
QueueUserWorkItem и уменьшаться после выполнения функции рабочего элемента. Выгрузка DLL допустима только после того, как этот счетчик обнулится.



Поделитесь с Вашими друзьями:
1   ...   27   28   29   30   31   32   33   34   ...   68


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал