Мегаобучалка Главная | О нас | Обратная связь


APC пользовательского режима



2019-07-03 170 Обсуждений (0)
APC пользовательского режима 0.00 из 5.00 0 оценок




Этот механизм можно использовать, если нужно выполнить какую-либо операцию (функцию) в контексте определенного потока. Для выполнения функции поток должен «дать согласие», перейдя в состояние тревожного ожидания (alertable wait state). Если поток находится в таком состоянии, то, как только мы поставим в очередь APC-запрос с указанием адреса функции и произвольного параметра для нее, поток перейдет к выполнению данной функции, после чего выйдет из состояния ожидания. APC пользовательского режима могут использовать функции ReadFileEx, WriteFileEx, а также SetWaitableTimer, о которой мы поговорим отдельно. Функции ReadFileEx и WriteFileEx предназначены специально для асинхронных операций – для них вы обязаны открывать файл (файл в самом общем смысле) в асинхронном режиме, указывая флаг FILE_FLAG_OVERLAPPED, а также для каждой операции создавать структуру OVERLAPPED. В качестве последнего параметра обе функции принимают адрес специальной функции завершения – FileIOCompletionRoutine. После завершения асинхронной операции, если поток находится в тревожном ожидании, эта функция будет вызвана с помощью механизма APC. В тревожное ожидание поток может перейти с помощью «расширенных» функций ожидания, которые оканчиваются на Ex. Это SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectsEx и другие. Для того чтобы вручную поместить APC-запрос в очередь потока, нужно воспользоваться функцией QueueUserAPC. Вот ее прототип:

DWORD QueueUserAPC( PAPCFUNC pfnAPC, // APC функция HANDLE hThread, // хендл потока ULONG_PTR dwData // параметр APC функции );

Рассмотрим небольшой пример ее использования (проверка ошибок устранена для повышения наглядности).

const int _SOME_MAGIC_VALUE = 5;   DWORD CALLBACK trd1(LPVOID p) { HANDLE hEvent = (HANDLE)p; SetEvent(hEvent);   int i = 0; while(i < _SOME_MAGIC_VALUE){ SleepEx(INFINITE, true); cout << i++ << endl; } return 0; }   VOID CALLBACK APCProc(ULONG_PTR dwParam) { cout << "APC Proc #" << dwParam; cout << " threadid :" << GetCurrentThreadId() << endl; }   int main() {  HANDLE hEvent = CreateEvent(0, false, false, NULL);   DWORD trd_id = 0; HANDLE hThread = CreateThread(0, 0, trd1, hEvent, 0, &trd_id); cout << "Thread id is 0x" << hex << trd_id << endl;   WaitForSingleObject(hEvent, INFINITE);   for(int i = 0;i < _SOME_MAGIC_VALUE;i++){ QueueUserAPC(APCProc, hThread, i); }   WaitForSingleObject(hThread, 1000); CloseHandle(hThread); return 0; }

Несмотря на кажущуюся простоту, пример довольно сложен, и не всегда можно предсказать, что будет на экране после его завершения. Давайте разберем его в форме вопрос-ответ.

Почему я синхронизирую потоки с помощью события?

Если этого не сделать, то все пять APC-запросов выполнятся еще до того, как функция потока trd1 получит управление. Это произойдет потому, что сама система в процессе создания потока использует механизм APC-вызовов для инициализации потока. С его помощью, например, происходит вызов всех функций DllMain с параметром DLL_THREAD_ATTACH, если, конечно, вы не вызывали DisableThreadLibraryCalls для какой-либо библиотеки.

Почему на экран выводится странный результат?

Thread id is 0x68c APC Proc #0 threadid :68c 0 APC Proc #1 threadid :68c APC Proc #2 threadid :68c APC Proc #3 threadid :68c APC Proc #4 threadid :68c 1

Как я уже говорил, для каждого потока система организует очередь из APC-запросов, так что в момент обработки первого запроса потоком система успевает добавить в очередь все остальные запросы, которые и выполняются при следующем вызове SleepEx. Результат сильно зависит от загруженности системы, так что у вас он может быть другим: например, все запросы успеют выполниться за одну итерацию.

Почему, если закомментировать тело APCProc, на экран выводится следующее?

Thread id is 0x7b4 0 1 2 3 4

Так как теперь эта процедура фактически ничего не делает, система не успевает добавить новый запрос в очередь до завершения обработки предыдущего, так что каждый SleepEx обрабатывает «свой» APC-запрос.

Теперь вам должно быть понятно, как использовать данный механизм для организации пула потоков. Вот примерный сценарий для фиксированного количества потоков в пуле: в главном потоке приложения создаются несколько рабочих потоков, каждый из которых сразу переходит в состояние тревожного ожидания специально созданного основным потоком события. Когда приходит клиентский запрос, главный поток передает APC-запрос одному из рабочих потоков. Рабочий поток пробуждается и выполняет функцию, поставленную в очередь главным потоком. При этом он не покидает функции WaitForSingleObjectEx. То есть выполнение APC-запроса производится как бы внутри функции WaitForSingleObjectEx. После завершения выполнения запроса управление передается функции WaitForSingleObjectEx, которая, в свою очередь, передает управление основному коду потока, возвращая WAIT_IO_COMPLETION.

При получении управления рабочий поток должен проанализировать значение, возвращенное этой функцией. Если оно равно WAIT_IO_COMPLETION, то причиной выхода из функции WaitForSingleObjectEx было завершение обработки APC-запроса – поток при этом должен снова перейти в состояние ожидания события. Если же возвращается значение WAIT_OBJECT_0, то причиной выхода была установка события в сигнальное состояние главным потоком приложения. При этом рабочий поток должен завершиться.

Это очень простая схема (например, рабочие потоки вместо ожидания могут выполнять какую-то другую полезную работу), но она довольно неплохо объясняет механизм использования APC для организации пула.

SetWaitableTimer

Эта функция появилась с версии 4.0. Она позволяет активировать таймер, который через заданный период времени в 100-наносекундных интервалах или при наступлении заданного абсолютного времени переходит в сигнальное состояние. Кроме этого, можно указать процедуру завершения, которая будет вызвана с помощью APC-запроса в данном потоке. Для процедуры завершения можно указать дополнительный параметр. Функция хороша тем, что она не привязана к окнам и циклу выборки сообщений, как, например, SetTimer. С ее помощью можно использовать таймеры в любых приложениях, включая консольные и сервисы. Однако у SetWaitableTimer есть и некоторые недостатки:

функция завершения всегда вызывается в потоке, вызвавшем SetWaitableTimer;

поток должен быть в состоянии тревожного ожидания, чтобы обработать APC-запрос;

второй APC-запрос начнет обрабатываться только после окончания обработки предыдущего запроса, то есть запросы обрабатываются последовательно.

Все эти проблемы решает объект "очередь таймеров", о котором речь пойдет позже.



2019-07-03 170 Обсуждений (0)
APC пользовательского режима 0.00 из 5.00 0 оценок









Обсуждение в статье: APC пользовательского режима

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:
Как построить свою речь (словесное оформление): При подготовке публичного выступления перед оратором возникает вопрос, как лучше словесно оформить свою...
Организация как механизм и форма жизни коллектива: Организация не сможет достичь поставленных целей без соответствующей внутренней...



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (170)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.007 сек.)