Что такое Completion Ports (IOCP)?

«Что такое Completion Ports (IOCP)?» — вопрос из категории Многопоточность, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

I/O Completion Ports (IOCP) — это механизм асинхронного ввода-вывода в Windows, предназначенный для создания высокопроизводительных масштабируемых сетевых серверов и приложений, работающих с множеством одновременных соединений.

Принцип работы: Вместо того чтобы блокировать поток на операции ввода-вывода (как в синхронной модели) или использовать множество потоков для ожидания событий (как в модели select/poll), IOCP уведомляет приложение о завершении операции. Приложение связывает сокеты (или файлы) с портом завершения, а затем инициирует асинхронные операции (например, WSARecv, WSASend). Когда операция завершается, система помещает уведомление о завершении в очередь порта. Потоки из специального пула (рабочие потоки) ожидают на этом порту (GetQueuedCompletionStatus) и обрабатывают результаты.

Пример структуры для передачи контекста операции:

#include <winsock2.h>
#include <windows.h>
#include <memory>

struct PerIoData {
    WSAOVERLAPPED overlapped; // Обязательная структура для асинхронных операций
    WSABUF wsaBuf;            // Буфер для данных
    char buffer[1024];
    SOCKET socket;            // Сокет, связанный с операцией
    // ... другие поля контекста
};

// Создание порта завершения
HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

// Связывание сокета с портом
CreateIoCompletionPort((HANDLE)clientSocket, completionPort, (ULONG_PTR)contextPtr, 0);

// Инициирование асинхронного чтения
auto perIoData = std::make_unique<PerIoData>();
perIoData->wsaBuf.buf = perIoData->buffer;
perIoData->wsaBuf.len = sizeof(perIoData->buffer);
perIoData->socket = clientSocket;

DWORD flags = 0;
WSARecv(clientSocket, &(perIoData->wsaBuf), 1, NULL, &flags, &(perIoData->overlapped), NULL);

// Рабочий поток: ожидание и обработка завершённых операций
DWORD bytesTransferred;
ULONG_PTR completionKey;
LPOVERLAPPED overlapped;
while (GetQueuedCompletionStatus(completionPort, &bytesTransferred, &completionKey, &overlapped, INFINITE)) {
    auto data = reinterpret_cast<PerIoData*>(overlapped);
    // Обработка данных в data->buffer, bytesTransferred содержит количество прочитанных байт
    // ...
}

Преимущества для C++ серверов:

  • Масштабируемость: Эффективное управление тысячами соединений с небольшим числом потоков (обычно по числу процессорных ядер).
  • Минимизация контекстных переключений: Система будит ровно столько потоков, сколько требуется для обработки завершённых операций.
  • Гибкость: Позволяет связывать произвольный контекст (completion key) с операцией для упрощения логики обработки.

В экосистеме Unix/Linux аналогичную роль выполняют механизмы epoll и io_uring.