Порты завершения ввода-вывода

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

Как работают порты завершения ввода-вывода

Функция CreateIoCompletionPort создает порт завершения ввода-вывода и связывает с ним один или несколько дескрипторов файлов. Когда асинхронная операция ввода-вывода для одного из этих дескрипторов файлов завершается, пакет завершения ввода-вывода помещается в очередь в порядке FIFO в соответствующий порт завершения ввода-вывода. Одним из эффективных способов использования этого механизма является объединение точки синхронизации для нескольких дескрипторов файлов в один объект, хотя существуют и другие полезные приложения. Обратите внимание, что, хотя пакеты помещаются в очередь в порядке FIFO, они могут быть выведены из очереди в другом порядке.

Примечание

Термин дескриптор файла , используемый здесь, относится к системной абстракции, представляющей перекрывающуюся конечную точку ввода-вывода, а не только файл на диске. Например, это может быть конечная точка сети, сокет TCP, именованный канал или почтовый слот. Можно использовать любой системный объект, поддерживающий перекрывающиеся операции ввода-вывода. Список связанных функций ввода-вывода см. в конце этого раздела.

 

Если дескриптор файла связан с портом завершения, переданный блок состояния не будет обновлен, пока пакет не будет удален из порта завершения. Единственное исключение — если исходная операция возвращается синхронно с ошибкой. Поток (созданный потоком main или самим потоком main) использует функцию GetQueuedCompletionStatus, чтобы ждать, пока пакет завершения будет помещен в очередь на порт завершения ввода-вывода, а не ожидает непосредственного завершения асинхронного ввода-вывода. Потоки, которые блокируют выполнение через порт завершения ввода-вывода, освобождаются в порядке последнего выхода (LIFO), а следующий пакет завершения извлекается из очереди FIFO порта завершения ввода-вывода для этого потока. Это означает, что при выпуске пакета завершения в поток система освобождает последний (последний) поток, связанный с этим портом, передав ему сведения о завершении для самого старого завершения ввода-вывода.

Хотя любое количество потоков может вызывать GetQueuedCompletionStatus для указанного порта завершения ввода-вывода, когда указанный поток вызывает GetQueuedCompletionStatus в первый раз, он становится связанным с указанным портом завершения ввода-вывода до тех пор, пока не произойдет одно из трех действий: поток завершит работу, задав другой порт завершения ввода-вывода или не закроет порт завершения ввода-вывода. Иными словами, один поток может быть связан не более чем с одним портом завершения ввода-вывода.

Когда пакет завершения помещается в очередь на порт завершения ввода-вывода, система сначала проверяет, сколько потоков, связанных с этим портом, запущено. Если число запущенных потоков меньше значения параллелизма (описано в следующем разделе), один из ожидающих потоков (последний) может обрабатывать пакет завершения. Когда выполняющийся поток завершает обработку, он обычно снова вызывает GetQueuedCompletionStatus , после чего он возвращается со следующим пакетом завершения или ожидает, если очередь пуста.

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

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

Примечание

Порт завершения ввода-вывода связан с процессом, который его создал, и не является совместно используемым между процессами. Однако один дескриптор можно совместно работать между потоками в одном процессе.

 

Потоки и параллелизм

Наиболее важным свойством порта завершения ввода-вывода, которое следует тщательно учитывать, является значение параллелизма. Значение параллелизма порта завершения указывается при его создании с помощью CreateIoCompletionPort с помощью параметра NumberOfConcurrentThreads . Это значение ограничивает количество запускаемых потоков, связанных с портом завершения. Когда общее число запускаемых потоков, связанных с портом завершения, достигает значения параллелизма, система блокирует выполнение всех последующих потоков, связанных с этим портом завершения, пока число запускаемых потоков не опустится ниже значения параллелизма.

Наиболее эффективный сценарий возникает при наличии пакетов завершения, ожидающих в очереди, но ожидания не могут быть удовлетворены, так как порт достиг предела параллелизма. Рассмотрим, что происходит с значением параллелизма одного и нескольких потоков, ожидающих в вызове функции GetQueuedCompletionStatus . В этом случае, если в очереди всегда есть пакеты завершения, ожидающие, когда запущенный поток вызывает GetQueuedCompletionStatus, он не будет блокировать выполнение, так как, как упоминалось ранее, очередь потока — LIFO. Вместо этого этот поток немедленно получит следующий пакет завершения в очереди. Переключения контекста потока не будут происходить, так как выполняющийся поток постоянно получает пакеты завершения, а другие потоки не могут выполняться.

Примечание

В предыдущем примере дополнительные потоки кажутся бесполезными и никогда не выполняются, но предполагается, что выполняющийся поток никогда не переводится в состояние ожидания каким-либо другим механизмом, завершает работу или иным образом закрывает связанный с ним порт завершения ввода-вывода. При проектировании приложения учитывайте все такие последствия выполнения потоков.

 

Лучшее общее максимальное значение для значения параллелизма — это количество ЦП на компьютере. Если транзакция требует длительных вычислений, большее значение параллелизма позволит выполнять больше потоков. Каждый пакет завершения может занять больше времени, но в то же время будет обрабатываться больше пакетов завершения. Вы можете поэкспериментировать со значением параллелизма в сочетании со средствами профилирования, чтобы добиться наилучшего эффекта для приложения.

Система также позволяет потоку, ожидая в GetQueuedCompletionStatus , обработать пакет завершения, если другой выполняемый поток, связанный с тем же портом завершения ввода-вывода, переходит в состояние ожидания по другим причинам, например функции SuspendThread . Когда поток в состоянии ожидания начинает работать снова, может возникнуть короткий период, когда количество активных потоков превышает значение параллелизма. Однако система быстро уменьшает это число, не разрешая новые активные потоки, пока число активных потоков не упадет ниже значения параллелизма. Это одна из причин, по которой приложение создает в своем пуле потоков больше потоков, чем значение параллелизма. Управление пулом потоков выходит за рамки область этого раздела, но рекомендуется иметь как минимум в два раза больше потоков в пуле потоков, чем процессоров в системе. Дополнительные сведения о пулах потоков см. в разделе Пулы потоков.

Поддерживаемые функции ввода-вывода

Следующие функции можно использовать для запуска операций ввода-вывода, которые выполняются с помощью портов завершения ввода-вывода. Чтобы включить механизм порта завершения ввода-вывода, необходимо передать функцию экземпляр структуры OVERLAPPED и дескриптор файла, ранее связанный с портом завершения ввода-вывода (путем вызова CreateIoCompletionPort).

Процессы и потоки

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus