45817 (Эффективная многопоточность), страница 4
Описание файла
Документ из архива "Эффективная многопоточность", который расположен в категории "". Всё это находится в предмете "информатика" из , которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "рефераты, доклады и презентации", в предмете "информатика, программирование" в общих файлах.
Онлайн просмотр документа "45817"
Текст 4 страницы из документа "45817"
И наоборот, если вам нужно каждый раз выполнять длительную операцию, укажите флаг WT_EXECUTELONGFUNCTION. Для каждой такой операции создается новый поток, который после ее обработки удаляется.
Характеристика | Значение |
Начальное коли-чество потоков в пуле | 0 |
Когда поток удаляется | Поток не имеет незавершенных операций ввода/вывода и простаивает некоторое время |
Способ ожидания, используемый потоком | Тревожное (alertable) ожидание |
Поток просыпается при | Приходе APC-запроса |
Таблица 4. Описание работы функции QueueUserWorkItem
Вызов функции при окончании асинхронной операции ввода/вывода
Если в программе выполняется большое количество операций ввода/вывода, лучше, чтобы они выполнялись асинхронно. Это намного повышает производительность приложения и уменьшает время отклика на клиентские запросы. Однако самое сложное в асинхронных операциях – правильно продолжить или завершить их. Можно связать все устройства (файлы) с портом завершения и обрабатывать окончания операций в отдельном потоке. Когда операций много, вновь встает вопрос об организации пула потоков для обработки асинхронных операций ввода/вывода. К счастью, у нас имеется хороший помощник:
BOOL BindIoCompletionCallback( // хендл файла HANDLE FileHandle, // функция обработки завершения запроса LPOVERLAPPED_COMPLETION_ROUTINE Function, // зарезервировано ULONG Flags ); |
Эта функция создает порт завершения и связывает его с файлом. Затем функция создает поток, который сразу же начинает ждать (GetQueuedCompletionStatus) окончания асинхронной операции, после чего вызывает определяемую вами функцию для обработки запроса. Вот ее прототип:
VOID CALLBACK FileIOCompletionRoutine( DWORD dwErrorCode, // код завершения DWORD dwNumberOfBytesTransfered, // количество переданных байтов LPOVERLAPPED lpOverlapped // структура OVERLAPPED ); |
Хотя прототип этой функции идентичен функции, вызываемой при окончании операций, начатых ReadFileEx и WriteFileEx, не стоит их путать. При использовании BindIoCompletionCallback эта функция вызывается с помощью порта завершения ввода/вывода, тогда как при использовании ReadFileEx и WriteFileEx функция вызывается с помощью APC.
Совершенно непонятно, почему в Microsoft решили не использовать флаги для этой функции, но факт остается фактом. И хотя Рихтер в своей статье, которая упоминалась выше, утверждает, что можно указать флаг WT_EXECUTEINIOTHREAD, это неправда. Вы можете сами посмотреть дизассемблером в ntdll.dll, например, функцию RtlSetIoCompletionCallback и убедиться, что третий параметр в ней просто не используется.
Как видно из прототипа, вы не можете передавать какого-либо дополнительного параметра функции FileIOCompletionRoutine, что может вызвать определенные проблемы. Самым распространенным решением является передача в функцию начала асинхронной операции структуры, производной от OVERLAPPED. Тогда в дополнительных членах структуры можно передавать какую угодно информацию – сама структура OVERLAPPED не копируется, везде передается только указатель на нее.
Характеристика | Значение |
Начальное коли-чество потоков в пуле | 0 |
Когда поток удаляется | Поток простаи-вает некоторое время |
Способ ожидания, используемый потоком | GetQueuedCompletionStatus |
Поток просыпается при | Постановке пакета запроса в очередь порта |
Таблица 5. Описание характеристик работы функции BindIoCompletionCallback
Периодический вызов функции
В самом начале статьи я обещал рассказать о новых «таймерных» функциях. До выхода Windows 2000 имелось три механизма периодического вызова пользовательских функций: «оконный» таймер, Multimedia-таймер и ожидающий таймер. У каждого из них были серьезные недостатки, к тому же они не поддерживали обработку запросов в пуле. Новые функции по созданию очереди таймеров более универсальны.
В Windows 2000 появился новый объект – очередь таймеров. Он основан на объекте исполнительной системы "ожидающий таймер", так что в качестве механизма обратного вызова используется APC. Создать объект можно с помощью следующей функции:
HANDLE CreateTimerQueue(VOID); |
Она возвращает хендл объекта "очередь таймеров" (queues of timers). После создания очереди в нее можно добавлять новые таймеры. Для этого используется функция CreateTimerQueueTimer:
BOOL CreateTimerQueueTimer( // указатель на хендл таймера PHANDLE phNewTimer, // хендл очереди таймеров HANDLE TimerQueue, // функция обратного вызова WAITORTIMERCALLBACK Callback, // параметр для функции обратного вызова PVOID Parameter, // время задержки перед первым вызовом в милисекундах DWORD DueTime, // период в милисекундах DWORD Period, // флаги ULONG Flags ); |
Рассмотрим параметры этой функции. Первый параметр возвращает хендл таймера, который можно использовать для вызова функций изменения таймера или его удаления (о них позже). Второй параметр – хендл очереди, созданной функцией CreateTimerQueue. В качестве него можно указать нулевое значение. В этом случае по умолчанию таймер будет добавлен к объекту "очередь таймеров". Третий параметр – адрес функции, которая будет вызвана при переходе таймера в сигнальное состояние. Вот ее прототип:
VOID CALLBACK WaitOrTimerCallback( PVOID lpParameter, // произвольный параметр BOOLEAN TimerOrWaitFired // причина вызова ); |
Произвольный параметр для нее указывается в функции CreateTimerQueueTimer четвертым параметром. Параметр TimerOrWaitFired для таймеров всегда равен TRUE.
Пятый параметр определяет, сколько времени в миллисекундах пройдет до первого вызова функции WaitOrTimerCallback. Если указать 0, то эта функция будет вызвана практически сразу (примерно так же быстро, как и в случае QueueUserWorkItem).
Шестой параметр задает период вызова пользовательской функции. В качестве этого параметра можно указать нулевое значение, тогда функция WaitOrTimerCallback будет вызвана только один раз.
В качестве флагов функции CreateTimerQueueTimer можно указывать все флаги из таблицы 2 и два новых:
Константа | Значение | Описание |
WT_EXECUTEINTIMERTHREAD | 0x20 | Пользовательская функция вызывается в потоке таймера |
WT_EXECUTEONLYONCE | 8 | Пользовательская функция вызывается только один раз |
Таблица 6. Флаги функции CreateTimerQueueTimer.
Если ваша функция WaitOrTimerCallback очень быстро отрабатывает, а количество запросов невелико – лучше всего указать флаг WT_EXECUTEINTIMERTHREAD. В этой ситуации функция будет вызвана в потоке, ожидающем таймера. Будьте осторожны – длительная блокировка пользовательской функции приведет к тому, что ожидающий поток не сможет обрабатывать приходящие запросы.
При указании флага WT_EXECUTEONLYONCE таймер будет установлен в сигнальное состояние только один раз.
Если вам больше не нужен таймер, его можно удалить из очереди с помощью функции DeleteTimerQueueTimer.
BOOL DeleteTimerQueueTimer( // хендл очереди таймеров HANDLE TimerQueue, // хендл таймера HANDLE Timer, // хендл объекта, устанавливаемого в сигнальное состояние после удаления HANDLE CompletionEvent ); |
Если используется очередь по умолчанию, в качестве первого параметра нужно передать NULL. Второй параметр – хендл удаляемого таймера. Третий параметр может принимать следующие значения:
INVALID_HANDLE_VALUE – означает, что вызывающая функция будет заблокирована до тех пор, пока таймер не обработает все текущие запросы. Вы должны быть осторожны с этим значением, так как вызов функции удаления таймера в самой пользовательской функции приведет к взаимоблокировке (deadlock).
NULL – если вы не хотите ожидать завершения обработки всех текущих запросов. Функция DeleteTimerQueueTimer возвратит управление немедленно.
Допустимый хендл объекта – если необходимо синхронизировать окончание обработки текущих запросов. Функция DeleteTimerQueueTimer возвратит управление немедленно, но после окончания обработки запросов объект завершения устанавливается в сигнальное состояние.
Можно сразу удалить всю очередь таймеров с помощью следующей функции:
BOOL DeleteTimerQueueEx( // хендл очереди таймеров HANDLE TimerQueue, // хендл объекта, устанавливаемого в сигнальное состояние после удаления HANDLE CompletionEvent ); |
Если удаляется очередь по умолчанию, в качестве первого параметра нужно передать NULL. Второй параметр имеет то же значение, что и в предыдущей функции DeleteTimerQueueTimer.
Кроме создания и удаления таймера в очереди, можно изменять некоторые его характеристики. Это делается вызовом функции ChangeTimerQueueTimer.
BOOL ChangeTimerQueueTimer( HANDLE TimerQueue, // хендл очереди таймеров HANDLE Timer, // хендл таймера ULONG DueTime, // новое значение задержки перед вызовом ULONG Period // новое значение периода вызова ); |
Функция вопросов не вызывает, однако нужно отметить, что она не оказывает влияния на «одноразовые» (one-shot) таймеры, при создании которых в качестве периода был указан 0.
Характеристика | Значение |
Начальное коли-чество потоков в пуле | 1 |
Когда поток удаляется | Когда удаляется последний таймер из очереди |
Способ ожидания, используемый потоком | Тревожное (alertable) ожидание |
Поток просыпается при | Приходе APC-запроса |
Таблица 7. Описание характеристик работы объекта "очередь таймеров".
Вызов функции при переходе объекта в сигнальное состояние
В приложении часто возникает необходимость дождаться какого-либо объекта и выполнить определенное действие. Для многопоточных приложений приходится для каждого такого случая заводить отдельный поток, что нехорошо. В этом случае на помощь приходит функция RegisterWaitForSingleObject. Она позволяет вызывать произвольную пользовательскую функцию после того, как заданный объект перейдет в сигнальное состояние. Причем никаких потоков создавать не нужно, все делается автоматически. Рассмотрим прототип этой функции:
BOOL RegisterWaitForSingleObject( PHANDLE phNewWaitObject, // адрес хендла объекта ожидания HANDLE hObject, // хендл объекта WAITORTIMERCALLBACK Callback, // функция обратного вызова PVOID Context // произвольный параметр ULONG dwMilliseconds, // таймаут ULONG dwFlags // флаги ); |
Первый параметр – это указатель на переменную, в которую будет возвращен хендл объекта ожидания. Нужно отметить, что на самом деле это не хендл объекта и его нельзя использовать, например, с функцией CloseHandle. Этот хендл можно использовать только для передачи функции UnregisterWait или UnregisterWaitEx (о них поговорим попозже). В качестве второго параметра нужно передать хендл объекта, перехода которого в сигнальное состояние ожидает эта функция. Третий параметр – адрес функции WaitOrTimerCallback, которую мы описывали раньше. Четвертый параметр – это любое значение, которое просто передается функции WaitOrTimerCallback. В качестве пятого параметра можно указать количество миллисекунд, которое определяет максимальное время ожидания объекта. После его истечения функция WaitOrTimerCallback будет вызвана со вторым параметром, равным TRUE. Если объект перешел в сигнальное состояние до истечения кванта времени, второй параметр функции WaitOrTimerCallback будет равным FASLE.
В качестве флагов можно указывать все описанные ранее значения и одно новое – WT_EXECUTEINWAITTHREAD. Его можно использовать, только если вы выполняете очень короткие операции, функция WaitOrTimerCallback будет вызвана в самом ожидающем потоке. Любая задержка в пользовательской функции приведет к тому, что поток не сможет обработать переход в сигнальное состояние объекта вовремя. Замечу, что при ожидании сигнального состояния события с ручным сбросом (manual reset event) не следует вызывать функцию PulseEvent, если не указаны флаги WT_EXECUTEINWAITTHREAD или WT_EXECUTEONLYONCE, так как в этом случае ожидающий поток не сможет обработать событие перехода объекта в сигнальное состояние.
Для остановки вызова пользовательской функции можно воспользоваться следующими функциями:
BOOL UnregisterWait( // хендл ожидания HANDLE WaitHandle ); BOOL UnregisterWaitEx( // хендл ожидания HANDLE WaitHandle, // хендл объекта, устанавливаемого в сигнальное состояние после удаления HANDLE CompletionEvent ); | |
Характеристика | Значение |
Начальное коли-чество потоков в пуле | 1 |
Когда поток удаляется | Когда количество объектов равно нулю |
Способ ожидания, используемый потоком | WaitForMultipleObjectsEx |
Поток просыпается при | Переходе объекта ядра в сигнальное состояние |
Таблица 8. Описание работы функции RegisterWaitForSingleObject