Поделиться через


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

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

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

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

Заметка

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

 

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

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

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

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

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

Заметка

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

 

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

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

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

Заметка

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

 

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

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

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

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

о процессах и потоках

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus