Реферат: Эффективная многопоточность
// хендл порта завершения ввода/вывода
HANDLE CompletionPort,
// количество переданных байт
LPDWORD lpNumberOfBytes,
// ключ завершения
PULONG_PTR lpCompletionKey,
// структура OVERLAPPED
LPOVERLAPPED *lpOverlapped,
// значение таймаута
DWORD dwMilliseconds
);
Эта функция блокирует поток до тех пор, пока порт не передаст потоку пакет запроса или не истечет таймаут.
Поместить пакет запроса в порт можно с помощью функции PostQueuedCompletionStatus.
BOOL PostQueuedCompletionStatus( HANDLE CompletionPort, // хендл порта завершения ввода/вывода DWORD dwNumberOfBytesTransferred, // количество переданных байт ULONG_PTR dwCompletionKey, // ключ завершения LPOVERLAPPED lpOverlapped // структура OVERLAPPED ); |
Пакет запроса не обязательно должен быть структурой OVERLAPPED или производной от нее [2].
Давайте соберем всю информацию воедино. Порт завершения – объект, организующий несколько очередей из клиентских запросов и потоков, их обрабатывающих. Поток добавляется в очередь ожидающих запрос потоков порта при вызове функции GetQueuedCompletionStatus. При поступлении запроса порт разблокирует первый поток в очереди ждущих потоков и передает ему этот запрос (в виде структуры OVERLAPPED и ключа завершения). Поток при этом перемещается в очередь активных потоков (число активных потоков увеличивается на 1). Предположим, у нас максимальное число активных потоков равно 1, тогда при поступлении следующего запроса другой поток из очереди ожидающих активирован не будет. После обработки клиентского запроса поток вновь вызывает GetQueuedCompletionStatus и ставится в начало списка ожидающих потоков. Почему поток ставится именно в начало списка? Дело в том, что потоки берутся из начала списка, и при низкой активности могут использоваться не все потоки. При этом стеки и контексты не используемых потоков могут быть выгружены на диск за ненадобностью.
Если в процессе обработки запроса поток обратился к блокирующей функции, число активных потоков уменьшается на 1, как если бы поток перешел снова в очередь ожидающих потоков. Это дает возможность при приходе следующего клиентского запроса задействовать следующий поток из очереди ожидающих. Когда первый поток закончит блокирующую операцию, число активных потоков превысит максимальное, и при следующем вызове функции GetQueuedCompletionStatus один из этих потоков заблокируется, а второй получит пакет запроса (если он имеется).
Очередь | Запись добавляется при: | Запись удаляется при: |
Список устройств, ассоциированных с портом | вызове CreateIoCompletionPort | закрытии хенда файла |
Очередь клиентских запросов (FIFO) | завершении асинхронной операции файла, ассоциированного с портом, или вызове функции PostQueuedCompletionStatus | передаче портом запроса потоку на обработку |
Очередь ожидающих потоков | вызове функции GetQueuedCompletionStatus | начале обработки клиентского запроса потоком |
Список работающих потоков | начале обработки клиентского запроса потоком | вызове потоком GetQueuedCompletionStatus или какую-либо блокирующей функции |
Список приостановленных потоков | вызове потоком какой-либо блокирующей функции | выходе потока из какой-либо блокирующей функции |
Таблица 1. Список очередей порта завершения ввода/вывода [1].
Недокументированные возможности порта и его низкоуровневое устройство
Как всегда это бывает у Microsoft, порт завершения обладает многими недокументированными возможностями:
У порта завершения ввода/вывода может быть имя, и соответственно, он доступен для других процессов. Совершенно непонятно, почему разработчики решили скрыть эту, на мой взгляд, нужную особенность порта. Имя можно задать в параметре ObjectAttributes функции NtCreateIoCompletion.
Вторая особенность вытекает из первой: с портом может быть связан дескриптор безопасности, который также задается в параметре ObjectAttributes функции NtCreateIoCompletion.
Открывается порт с помощью функции NtOpenIoCompletion. При вызове функции нужно указать имя порта и уровень доступа. В качестве уровня доступа можно указывать все стандартные и следующие специальные права [2] (таблица 2).
Символическое обозначение | Константа | Описание |
IO_COMPLETION_QUERY_STATE | 1 | Необходим для запроса состояния объекта "порт" |
IO_COMPLETION_MODIFY_STATE | 2 | Необходим для изменения состояния объекта "порт" |
Таблица 2.