MMORPG - сеетвые техСтатьи

Модель порта завершения

Автор:

Copyright by О. Б. Люсин, асс. проф. Института Транспорта и Связи, Рига.

Модель порта завершения
Рабочие потоки и порты завершения
Текст программы
Прoграммы для работы с портом завершения IOCP
  Программа-клиент с использованием потока
  Программа-сервер с использованием модели IOCP

Модель порта завершения

Последняя модель ввода/вывода, которую мы рассмотрим, это модель "порта завершения" – completion port. Порт завершения представляет собой специальный механизм в составе ОС, с помощью которого приложение использует объединение (пул) нескольких потоков, предназначенных единственно для цели обработки асинхронных операций ввода/вывода с перекрытием.

Приложения, которые вынуждены обрабатывать многочисленные асинхронные запросы (речь идет о сотнях и тысячах одновременно поступающих запросах – например, на поисковых серверах или популярных серверах типа www.microsoft.com), с помощью этого механизма могут обрабатывать I/O- запросы существенно быстрее и эффективнее, чем просто запускать новый поток для обработки поступившего запроса. Поддержка этого механизма включена в Windows NT, Windows 2000, Windows XP и Windows Server 2003 и особенно эффективна для мультипроцессорных систем. Так, демонстрационный программный код, который опубликован в MSDN, рассчитан на 16-ти процессорную аппаратную платформу.

Для функционирования этой модели необходимо создание специального программного объекта ядра системы, который и был назван "порт завершения". Это осуществляется с помощью функции CreateIoCompletionPort(), которая асссоциирует этот объект с одним или несколькими файловыми (сокетными) дескрипторами (см. ниже пример в разделе 4.5.1.1) и который будет управлять перекрывающимися I/O операциями, используя определенное количество потоков для обслуживания завершенных запросов.

Для начала нам необходимо создать программный объект - порт завершения I/O, который будет использоваться, чтобы управлять множественными I/O-запросами для любого  количества сокетных дескрипторов. Это выполняется  вызовом функции CreateIoCompletionPort(), которая определена как:

HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,
    HANDLE ExistingCompletionPort,
    DWORD CompletionKey,
    DWORD NumberOfConcurrentThreads
);

Прежде чем рассматривать параметры подробно, следует отметить, что эта функция фактически используется для двух различных целей:

1.  Чтобы создать объект порта завершения
2.  Связать дескриптор с портом завершения

Когда Вы первоначально создаете объект порта завершения, интерес представляет единственный параметр - NumberOfConcurrentThreads; первые три параметра не существенны. Параметр NumberOfConcurrentThreads специфичен, потому что он определяет число потоков, которым позволяется выполниться одновременно на порте завершения. По идее, нам нужен только один поток для каждого отдельного процессора, чтобы обслужить порт завершения и избежать переключения контекста потока. Значение для этого параметра равное 0 сообщает системе разрешить иметь столько потоков, сколько  процессоров имеется в системе. Следующий вызов создает порт завершения I/O:

CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0, 0);

В результате функция  возвратит дескриптор, который используется, чтобы идентифицировать порт завершения, когда на него будет назначен дескриптор сокета.

Рабочие потоки и порты завершения

После того, как порт завершения успешно создан, Вы можете начинать связывать дескрипторы сокета с объектом. Перед этим – и это важно - Вы должны создать один или большее количество рабочих потоков, чтобы обслужить порт завершения, когда  пакеты завершения операции для сокета отправлены в объект порта завершения. Можно задаться вопросом, сколько же потоков должно быть создано, чтобы обслужить порт завершения? Это один из более сложных аспектов модели порта завершения, потому что  количество потоков, необходимое для обслуживания запросов Ввода – вывода, зависит от общей концепции проекта вашего приложения. Важно обратить внимание на различие между числом заявленных параллельных потоков при работе с CreateIoCompletionPort() и числом рабочих потоков; они не представляют одно и то же.

Обычно рекомендуется, чтобы  функция CreateIoCompletionPort() задавала один поток для каждого отдельного процессора во избежание переключения контекста потока. Параметр NumberOfConcurrentThreads функции CreateIoCompletionPort() явно дает разрешение системе позволять только n потокам работать одновременно на порте завершения. И даже если Вы создаете больше чем n рабочих потоков для порта завершения, только n потокам будет разрешено работать одновременно. (Фактически, система могла бы превысить это значение на короткий промежуток времени, но она же быстро приведет количество потоков до значения, которое Вы определяете в CreateIoCompletionPort().)

Можно задаться вопросом, для каких целей надо создавать большее количество рабочих потоков, чем указано параметром вызова CreateIoCompletionPort()?

Как было упомянуто выше, это зависит от идеологии построения проекта приложения в целом. Некоторые потоки могут приостанавливать своё выполнение (например proxy-сервер - ждёт ответа с помощью функции WaitForSingleObject()) или поток "засыпает" на Sleep() - тогда в это время вместо него с портом завершения сможет работать другой поток. Т.е. если программа будет блокировать поток - тогда лучше создать рабочих потоков несколько больше чем NumberOfConcurrentThreads, с другой стороны — если в вашей программе не будет блокировки - тогда не стоит создавать лишних потоков. Однако по большому счёту можно считать NumberOfConcurrentThread константой.

Как только создано достаточно рабочих потоков, чтобы обслужить запросы ввода/вывода на порте завершения, можно начинать связывать дескрипторы сокета с портом завершения. Это требует вызова функции CreateIoCompletionPort() на уже существующем порте завершения и указания первых трех параметров — FileHandle, ExistingCompletionPort и CompletionKey с соответствующей сокетной информацией.

Параметр FileHandle представляет дескриптор сокета, ассоциированный с портом завершения. Параметр ExistingCompletionPort указывает существующий уже порт завершения, с которым будет связан дескриптор сокета. Параметр CompletionKey задает специфические данные "per-handle data",  которые можно связать с конкретным сокетным дескриптором. Прикладные программы могут хранить любой тип информации, связанной с сокетом,  используя этот ключ. Мы называем эти данные "per-handle data" или "сокетная информация". Принято хранить дескриптор сокета, используя этот ключ как указатель на структуру данных, содержащую дескриптор сокета и другую "сокет-специфичную" информацию. Функции потока, которые обслуживают порт завершения, могут отыскивать эту специфичную для данного сокета информацию, используя этот ключ.

Поток использует функцию для ожидания пакета завершения, который будет поставлен в очередь в порту завершения, а не ожидает непосредственного исполнения асинхронного ввода/вывода. Потоки, которые блокируются на порту завершения,  освобождаются в порядке LIFO. Это означает, что, когда пакет завершения поставлен в очередь порта завершения, система освобождает последний поток, который был заблокирован на порту.

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

Далее повторяем шаги 5—8, пока сервер не завершит свою работу.

Страницы: 1 2 3 4 Следующая »

24 июля 2007 (Обновление: 19 дек 2008)

Комментарии [8]