Windows

       

Программа-пример WaitForMultExp


Эта программа, «10 WaitForMultExp.exe» (см. листинг на рис. 10-4), предназначена для тестирования функции WaitForMultipleExpressions Файлы исходного кода и ресурсов этой программы находятся в каталоге l0-WaitForMultExp на компакт-диске, прилага емом к кпиге. После запуска WaitForMultExp открывается диалоговое окно, показан ное ниже.

Если Вы нс станете изменять предлагаемые параметры, а просто щелкнете кноп ку Wait For Multiple Expressions, диалоговое окно будет выглядеть так, как показано на следующей иллюстрации.

Программа создаст четыре объекта ядра «событие» в занятом состоянии и поме щает в многоколоночный список (с возможностью выбора сразу нескольких элемен тов) по одной записи для каждого объекта ядра. Далее программа анализирует содер жимое поля Expression и формирует массив описателей. По умолчанию я предлагаю объекты ядра и выражение, как в предыдущем примере.

Поскольку я задал время ожидания равным 30000 мс, у Вас есть 30 секунд на вне сение изменений. Выбор элемента в нижнем списке приводит к вызову SetEvent, ко

торая освобождает объект, а отказ от его выбора — к вызову ResetEvent и соответствен но к переводу объекта в занятое состояние. После выбора достаточного числа эле ментов (удовлетворяющего одному из выражений) WaitForMultipleExpressions возвра щает управление, и в нижней части диалогового окна показывается, какому выраже нию удовлетворяет Ваш выбор. Если Вы не уложитесь в 30 секунд, появится слово "Timeout"

Теперь обсудим мою функцию WaitForMultipleExpressions. Реализовать ее было не просто, и ее применение, конечно, приводит к некоторым издержкам. Как Вы знаете, в Windows есть функция WaitForMultipleOtyects, которая позволяет потоку ждать по единственному AND-выражснию.

DWORD WaitForMultipleObjects( DWORD dwObjects, CONST HANDLE* pliObjects, BOOL fWaitAll, DWORD dwMimseconds);

Чтобы расширить ее функциональность для поддержки выражений, объединяемых OR, я должен создать несколько потоков — по одному на каждое такое выражение Каждый из этих потоков ждет в вызове WaitForMultipleObjectsEx по единственному AND-выражснию (Почему я использую эту функцию вместо более распространенной WaitForMultipleObjects - станет ясно позже) Когда какое-то выражение становится истинным, один из созданных потоков пробуждается и завершается




Поток, который вызвал WaitForMultipleExpressions (и который породил все OR-пo токи), должен ждать, пока одно из OR-выражений пе станет истинным. Для этого он вызывает функцию WaitForMultipleQbjeclsEx. В параметре dwObjects передается коли чество порожденных потоков (OR-выражений), а параметр phObjects указывает на массив описателей этих потоков. В паряметр fWaitAll записывается FALSE, чтобы ос новной поток пробудился сразу после того, как оанет истинным любое из выраже ний. И, наконец, в параметре dwMilliseconds передается значение, идентичное тому, которое было указано в аналогичном параметре при вызове WaitForMultipleExpressions



Если в течение заданного времени ни одно из выражений не становится истин ным, WaitForMultipleObjectsEx возвращает WAIT_TIMHOUT, и это же значение возвpa щается функцией WaitForMiltipleExpressions А если какое-нибудь выражение становит

ся истинным, WaitForMultipleObjectsEx возвращает индекс, указывающий, какой поток завершился Так как каждый поток представляет отдельное выражение, этот индекс сообщает и то, какое выражение стало истинным; этот же индекс возвращается и функцией WaitForMultipleExpressions.

На этом мы, пожалуй, закончим рассмотрение того, как работает функция WaiWor MultipleExpressions. Но нужно обсудить еще три вещи. Во-первых, нельзя допустить, чтобы несколько OR-потоков одновременно пробудились в своих вызовах WaitFor MultipleObjectsEx, так как успешное ожидание некоторых объектов ядра приводит к изменению их состояния (например, у семафора счетчик уменьшается на 1) WaitFor MultipleExpressions ждет лишь до тех пор, пока одно из выражений не станет истин ным, а значит, я должен предотвратить более чем однократное изменение состояния объекта

Решить эту проблему на самом деле довольно легко. Прежде чем порождать OR потоки, я создаю собственный объект-семафор с начальным значением счетчика, равным 1 Далее каждый OR-поток вызывает WaitForMultipleObjectsEx и передает ей не только описатели объектов, связанных с выражением, но и описатель этого семафо ра. Теперь Вы понимаете, почему в каждом наборе не может быть более 63 описате лей? Чтобы OR-поток пробудился, должны освободиться все объекты, которые он ждет, — в том числе мой специальный семафор. Поскольку начальное значение его счетчика равно 1, более одного OR-потока никогда не пробудится, и, следовательно, случайного изменения состояния каких-либо других объектов нс произойдет.



Второе, на что нужно обратить внимание, - как заставить ждущий поток прекра тить ожидание для корректной очистки. Добавление семафора гарантирует, что про будится не более чем один поток, но, раз мне уже известно, какое выражение стало истинным, я должен пробудить и остальные потоки, чтобы они корректно заверши лись. Вызова TerminateThread следует избегать, поэтому нужен какой-тодругой меха низм. Поразмыслив, я вспомнил, что потоки, ждущие в «тревожном" состоянии, при нудительно пробуждаются, когда в АРС-очереди появляется какой-нибудь элемент.

Моя реализация WaitForMultipleExpressions для принудительного пробуждения по токов использует QueueUserAPC. После того как WaitForMultipleObjects, вызванная ос новным потоком, возвращает управление, я ставлю АРС-вызов в соответствующие очереди каждого из все еще ждущих OR-потоков:

// выводим все еще ждущие потоки из состояния сна,
// чтобы они могли корректно завершиться

for (dwExpNum = 0; dwExpNum < dwNumExps; dwExpNum++)
{

if ((WAIT_TIMEOUT == dwWaitRet) (dwExpNum != (dwWaitRet - WAIT_OBJECT_0)))
{
QueueUserAPC(WFME_ExpressionAPC, ahThreads[dwExpNum], 0);
}

}

Функция обратного вызова, WFMEExpressionAPC, выглядит столь странно пото му, что на самом деле от нее не требуется ничего, кроме одного: прервать ожидание потока.

// это АРС-функция обратного вызова

VOID WINAPI WFHE_ExpressionAPC(DWORD dwData}
{

// в тело функции преднамеренно не включено никаких операторов

}

Третье (и последнее) — правильная обработка интервалов ожидания. Если ника кие выражения так и не стали истинными в течение заданного времени, функция

WaitForMultipleObjects, вызванная основным потоком, возвращает WAIT_TIMEOUT В этом случае я должен позаботиться о том, чтобы ни одно выражение больше не ста ло бы истинным и тем самым не изменило бы состояние объектов. За это отвечает следующий код

// ждем, когда выражение станет TRUE или когда истечет срок ожидания
dwWaitRet = WaitForMultiplcObjects(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_TIMbOUT;
}
}

Я не даю другим выражениям стать истинными за счет ожидания на семафоре. Это приводит к уменьшению счетчика семафора до 0, и никакой OR-поток не может про будиться. Но где-то после вызова функции WaitForMultipleObjects из основного пото ка и обращения той к WaitForSingleObject одно из выражений может стать истинным Вот почемуя проверяю значение, возвращаемое WaitForSingleQbject. Если она возвра щает WAIT_OBJECT_0, значит, семафор захвачен основным потоком и ни одно из выражений не стало истинным. Но если она возвращает WAIT_TIMEOUT, какое-то выражение все же стало истинным, прежде чем основной поюк успел захватить се мафор. Чтобы выяснить, какое именно выражение дало TRUE, основной поток снова вызывает WaitForMultipleObjects, но уже с временем ожидания, равным INFINITE; здесь все в порядке, так как я знаю, что семафор захвачен OR-потоком и этот поток вот-вот завершится Теперь я должен пробудить остальные OR-потоки, чтобы корректно за вершить их Это делается в цикле, из которого вызывается QueueUserAPC (о ней я уже рассказывал).

Поскольку реализация WintForMultipleExpressions основана на использовании груп пы потоков, каждый из которых ждет на своем наборе объектов, объединяемых по AND, мьютексы в ней неприменимы. В отличие от остальных объектов ядра мьютек сы могут передаваться потоку во владение. Значит, если какой-нибудь из моих AND потоков заполучит мьютекс, то по его завершении произойдет отказ от мьютекса. Вот когда Microsoft добавит в Windows API функцию, позволяющую одному потоку пере давать права на владение мьютексом другому потоку, тогда моя функция WaitFor MultipleExpressions и сможет поддерживать мьютексы. А пока надежного и корректного способа ввести в WattForMultipleExpressions такую поддержку я не вижу.

WaitForMultExp


Содержание раздела