Сценарий 3: вызов функций при освобождении отдельных объектов ядра
Microsoft обнаружила, что во многих приложениях потоки порождаются только для того, чтобы ждать на тех или иных объектах ядра. Как только объект освобождается, поток посылает уведомление и снова переходит к ожиданию того же объекта. Неко торые разработчики умудряются писать программы так, что в них создается несколь ко потоков, ждущих один объект. Это невероятное расточительство системных ресур сов Конечно, издержки от создания потоков существенно меньше, чем от создания процессов, но и потоки не воздухом питаются У каждого из них свой стек, не говоря уж об огромном количестве команд, выполняемых процессором при создании и унич тожении потока Поэтому надо стараться сводить любые издержки к минимуму.
Если Вы хотитe зарегистрировать рабочий элемент так, чтобы он обрабатывался при освобождении какого-либо объекта ядра, используйте еще одну новую функцию пула потоков
BOOL RegisterWaitForSingleOb]ect( PHANOLE phNewWaitObject, HANDLE hObject, WAITORTIMERCALLBACK pfnCallback, PVOIO pvContext, ULONG dwMilliseconrts, ULONG dwFlags);
Эта функция передает Ваши параметры компоненту поддержки ожидания в пуле , потоков. Вы сообщаете ему, что рабочий элемент надо поставить в очередь, как толь
ко освободится объект ядра (на который указывает bObject) Кроме того, Вы можете задать ограничение по времени, т. e. элемент будет помещен в очередь через опреде ленное время, даже если объект ядра так и нс освободится (При этом допустимы значения INFINITE и 0.) В общем, эта функция похожа на хорошо известную функ цию WattForSingIeObject (см. главу 9). Зарегистрировав рабочий элемент на ожидание указанного объекта, RegisterWaitForStngleObject возвращает в параметре phNewWait Object описатель, идентифицирующий объект ожидания
Данный компонент реализует ожидание зарегистрированных объектов через Wait ForMultipleObjects и поэтому накладывает те же ограничения, что и эта функция Одно из них заключается в том, что нельзя ожидать тот жс объект несколько paз. Так что придется вызывать DuplicateHandle и отдельно регистрировать исходный и продуб лированный описатель Вам должно быть известно, что единовременно функция WaitForMultipleObjects способна отслеживать не болсе 64 (MAXIMUM_WAIT_OBJECTS) объектов А что будет, если попробовить зарегистрировать с ее помощью более 64 объектов? Компонент поддержки ожидания создаст еще один поток, который тоже вы зовет WaitForMultipleObjects. (На самом деле новый поток создается на каждые 63 объек та, потому что потокам приходится использовать объект ядра «ожидаемый таймер", контролирующий таймауты)
По умолчанию рабочий элемент, готовый к обработке, помещается в очередь к потокам компонента поддержки других операций (не связанных с вводом-выводом). В конечном счете один из его потоков пробудится и вызовет Вашу функцию, у кото рой должен быть следующий прототип.
VOID WINAPI WaitOrTimerCallbackFunc( PVOID pvContext, BOOLEAN fTimerOrWaitFired);
Параметр pfTimerOrWaitFired принимает значение TRUE, если время ожидания ис текло, или FALSE, если объект освободился раньше.
В параметре dwFlags функции RegisterWaitForSingleObject можно передать флаг WT_EXECUTEINWAITTHREAD, который заставляет выполнить функцию рабочего эле мента в одном из потоков компонента поддержки ожидания. Это эффективнее, пото му что тогда рабочий элемент не придется ставить в очередь компонента поддержки других операций. Но в то же время и опаснее, так как этот поток не сможет ждать освобождения других объектов. Используйте этот флаг, только если Ваша функция выполняется быстро
Вы можете также передать флаг WT_EXECUTEINIOTHREAD, если Ваш рабочий эле мент выдаст запрос на асинхронный ввод-вывод, или WT_EXECUTEINPERSISTENT THREAD, если ему понадобится операция с использованием постоянно существующе го потока. В случае длительного выполнения функции рабочего элемента можно при менить флаг WT_EXECUTELONGFUNCTION Указывайте этот флаг, только если рабо чий элемент передается компоненту поддержки ввода-вывода или других операций, — функцию, требующую продолжительной обработки, нельзя выполнять в потоке, ко торый относится к компоненту поддержки ожидания.
И последний флаг, о котором Вы должны знать, — WT_EXECUTEONLYONCE. До пустим, Вы зарегистрировались на ожидание объекта ядра "процесс" После перехо да в свободное состояние он так и останется в этом состоянии, что заставит компо нент поддержки ожидания постоянно включать в очередь рабочие элементы. Так вот, чтобы избежать этого, Вы можете использовать флаг WT_EXECUTEONLYONCE — он сообщает пулу потоков прекратить ожидание объекта после первой обработки рабо чего элемента.
Теперь представьте, что Вы ждете объект ядра "событие с автосбросом": сразу после освобождения он тут же возвращается в занятое состояние; при этом в очередь ста вится соответствующий рабочий элемент. На этом этапе пул продолжает отслеживать объект и снова ждет его освобождения или того момента, когда истечет время, выде ленное на ожидание. Если состояние объекта Вас больше не интересует, Вы должны снять его с регистрации. Это необходимо даже для отработавших объектов, зарегис трированных с флагом WT_EXECUTEONLYONCE. Вот как выглядит требуемая для этого функция:
BOOL UnregisterWaitEx( HANOLE hWaitHandle, HANDLE hCompletionEvent);
Первый параметр указывает на объект ожидания (его описатель возвращается RegisterWaitForSingleObject), а второй определяет, каким образом Вас следует уведом лять о выполнении последнего элемента в очереди. Как и в DeleteTimerQueueTimer, Вы можете передать в этом параметре NULL (если уведомление Вас не интересует), INVA LID_HANULEVALUF, (функция блокируется до завершения обработки всех элементов в очереди) или описатель объекта-события (переходящего в свободное состояние при завершении обработки очередного элемента). В ответ на неблокирующий вызов Unre gisterWaitEx возвращает TRUE, если очередь пуста, и FALSE в ином случае (при этом GetLastError возвращает STATUS_PENDING).
И вновь будьте осторожны, передавая значение INVALIDHANDLE_VALUE. Функция рабочего элемента заблокирует сама себя, если попытается снять с регистрации выз вавший cc объект ожидания. Такая попытка подобна команде: приостановить меня, пока я не закончу выполнение, — полный тупик. Но UnregisterWaitEx разработана так, чтобы предотвращать тупиковые ситуации, когда поток компонента поддержки ожи дания выполняет рабочий элемент, а тот пытается снять с регистрации запустивший его объект ожидания. И еще один момент: не закрывайте описатель объекта ядра до тех пор, пока не снимете его с регистрации. Иначе недействительный описатель по падет в WaitForMultipleObjects, к которой обращается поток компонента поддержки ожидания. Функция моментально завершится с ошибкой, и этот компонент переста нет корректно работать.
И последнее- никогда не вызывайте PulseEvent для освобождения объекта-события, зарегистрированного на ожидание. Поток компонента поддержки ожидания скорее всего будет чем-то занят и пропустит этот импульс от PulseEvent. Но эта проблема для Вас не нова — PulseEvent создает ее почти во всех архитектурах поддержки потоков