I/O 완료 포트

I/O 완료 포트는 다중 프로세서 시스템에서 여러 비동기 I/O 요청을 처리하기 위한 효율적인 스레딩 모델을 제공합니다. 프로세스에서 I/O 완료 포트를 만드는 경우, 시스템은 이러한 요청을 처리하는 것이 유일한 목적인 스레드를 위해 연결된 큐 개체를 만듭니다. 많은 동시 비동기 I/O 요청을 처리하는 프로세스는 미리 할당된 스레드 풀과 함께 I/O 완료 포트를 사용함으로써 I/O 요청을 받았을 때 스레드를 만드는 것보다 더 빠르고 효율적으로 처리할 수 있습니다.

I/O 완료 포트 작동 방식

CreateIoCompletionPort 함수는 I/O 완료 포트를 만들고 하나 이상의 파일 핸들을 해당 포트와 연결합니다. 이러한 파일 핸들 중 하나에 대한 비동기 I/O 작업이 완료되면 I/O 완료 패킷이 연결된 I/O 완료 포트에 대한 FIFO(선입선출) 순서로 큐에 대기됩니다. 다른 유용한 애플리케이션도 존재하지만 이 메커니즘의 한 가지 강력한 용도는 여러 파일 핸들의 동기화 지점을 단일 개체로 결합하는 것입니다. 패킷이 FIFO 순서로 큐에서 대기하는 동안 다른 순서로 큐에서 제거될 수 있다는 점에 유의하세요.

참고

여기서 사용되는 용어 파일 핸들은 디스크의 파일뿐만 아니라 겹치는 I/O 엔드포인트를 나타내는 시스템 추상화를 의미입니다. 예를 들어 네트워크 엔드포인트, TCP 소켓, 명명된 파이프 또는 메일 슬롯일 수 있습니다. 겹치는 I/O를 지원하는 모든 시스템 개체를 사용할 수 있습니다. 관련 I/O 함수 목록은 이 항목의 끝을 참조하세요.

 

파일 핸들이 완료 포트와 연결되면 완료 포트에서 패킷이 제거될 때까지 전달된 상태 블록이 업데이트되지 않습니다. 유일한 예외는 원래 작업이 오류와 함께 동기적으로 반환되는 경우입니다. 스레드(주 스레드 또는 주 스레드 자체에서 만든 스레드)는 비동기 I/O가 완료될 때까지 직접 기다리지 않고 GetQueuedCompletionStatus 함수를 사용하여 완료 패킷이 I/O 완료 포트에 큐에 대기할 때까지 기다립니다. I/O 완료 포트에서 실행을 차단하는 스레드는 LIFO(Last-in-first-out) 순서로 해제되고, 다음 완료 패킷은 해당 스레드에 대한 I/O 완료 포트의 FIFO 큐에서 풀됩니다. 즉, 완료 패킷이 스레드에 릴리스되면 시스템은 해당 포트와 연결된 마지막(가장 최근) 스레드를 릴리스하여 가장 오래된 I/O 완료에 대한 완료 정보를 전달합니다.

지정된 I/O 완료 포트에 대해 GetQueuedCompletionStatus를 호출할 수 있는 스레드는 많지만 지정된 스레드가 GetQueuedCompletionStatus를 처음 호출하면 스레드가 종료되거나 다른 I/O 완료 포트를 지정하거나 I/O 완료 포트를 닫을 때까지 지정된 I/O 완료 포트와 연결됩니다. 즉, 단일 스레드는 최대 하나의 I/O 완료 포트와 연결될 수 있습니다.

완료 패킷이 I/O 완료 포트의 큐에서 대기하는 경우 시스템은 먼저 해당 포트와 연결된 실행 중인 스레드 수를 확인합니다. 실행 중인 스레드 수가 동시성 값(다음 섹션에서 설명)보다 작으면 대기 중인 스레드(가장 최근 스레드) 중 하나가 완료 패킷을 처리할 수 있습니다. 실행 중인 스레드가 처리를 완료하면 일반적으로 GetQueuedCompletionStatus를 다시 호출합니다. 이때 다음 완료 패킷과 함께 반환되거나 큐가 비어 있는 경우 대기합니다.

스레드는 PostQueuedCompletionStatus 함수를 사용하여 I/O 완료 포트의 큐에 완료 패킷을 배치할 수 있습니다. 이렇게 하면 완료 포트를 사용하여 I/O 시스템에서 I/O 완료 패킷을 받는 것 외에도 프로세스의 다른 스레드로부터 통신을 받을 수 있습니다. PostQueuedCompletionStatus 함수를 사용하면 애플리케이션이 비동기 I/O 작업을 시작하지 않고도 고유한 특수 용도의 완료 패킷을 I/O 완료 포트의 큐에 대기시킬 수 있습니다. 예를 들어 작업자 스레드에 외부 이벤트를 알리는 데 유용합니다.

I/O 완료 포트 핸들 및 해당 특정 I/O 완료 포트와 연결된 모든 파일 핸들을 I/O 완료 포트에 대한 참조라고 합니다. I/O 완성 포트는 더 이상 참조가 없을 때 릴리스됩니다. 따라서 I/O 완료 포트 및 관련 시스템 리소스를 릴리스하려면 이러한 핸들을 모두 올바르게 닫아야 합니다. 이러한 조건이 충족되면 애플리케이션은 CloseHandle 함수를 호출하여 I/O 완료 포트 핸들을 닫아야 합니다.

참고

I/O 완료 포트는 해당 I/O 완료 포트를 만든 프로세스와 연결되며 프로세스 간에 공유할 수 없습니다. 그러나 단일 핸들은 동일한 프로세스에 속한 스레드 간에 공유될 수 있습니다.

 

스레드 및 동시성

신중하게 고려해야 할 I/O 완료 포트의 가장 중요한 속성은 동시성 값입니다. 완료 포트의 동시성 값은 NumberOfConcurrentThreads 매개 변수를 통해 CreateIoCompletionPort를 사용하여 만들 때 지정됩니다. 이 값은 완료 포트와 연결된 실행 가능한 스레드 수를 제한합니다. 완료 포트와 연결된 실행 가능한 스레드의 총 수가 동시성 값에 도달하면 시스템은 실행 가능한 스레드의 수가 동시성 값 아래로 떨어질 때까지 해당 완료 포트와 연결된 후속 스레드의 실행을 차단합니다.

가장 효율적인 시나리오에서는 큐에서 대기 중인 완료 패킷이 있지만 포트가 동시성 제한에 도달했기 때문에 대기를 충족할 수 없는 경우에 발생합니다. GetQueuedCompletionStatus 함수 호출에서 대기 중인 하나 및 여러 스레드의 동시성 값이 어떻게 되는지 고려합니다. 이 사례에서 큐에 항상 완료 패킷이 대기 중인 경우, 실행 중인 스레드가 GetQueuedCompletionStatus를 호출할 때 앞에서 설명한 대로 스레드 큐가 LIFO이므로 실행을 차단하지 않습니다. 대신 이 스레드는 대기 중인 다음 완료 패킷을 즉시 선택합니다. 실행 중인 스레드가 계속해서 완료 패킷을 선택하고 다른 스레드를 실행할 수 없으므로 스레드 컨텍스트 전환이 발생하지 않습니다.

참고

이전 예제에서 추가 스레드는 쓸모가 없고 실행되지 않는 것처럼 보이지만 실행 중인 스레드가 다른 메커니즘에 의해 대기 상태로 설정되거나, 종료되거나, 다른 방법으로 연결된 I/O 완료 포트를 닫지 않는다고 가정합니다. 애플리케이션을 설계할 때 이러한 모든 스레드 실행 파급 효과를 고려합니다.

 

동시성 값으로 선택할 수 있는 가장 좋은 전체 최대값은 컴퓨터의 CPU 수입니다. 트랜잭션에 긴 계산이 필요한 경우 더 큰 동시성 값을 사용하면 더 많은 스레드를 실행할 수 있습니다. 각 완료 패킷을 완료하는 데 시간이 더 오래 걸릴 수 있지만 더 많은 완료 패킷이 동시에 처리됩니다. 프로파일링 도구와 함께 동시성 값을 실험하여 애플리케이션에 가장 적합한 효과를 얻을 수 있습니다.

또한 동일한 I/O 완료 포트와 연결된 다른 실행 중인 스레드가 SuspendThread 함수와 같은 다른 이유로 대기 상태로 전환되는 경우 시스템은 GetQueuedCompletionStatus에서 대기 중인 스레드가 완료 패킷을 처리하도록 할 수 있습니다. 대기 상태의 스레드가 다시 실행되기 시작하면 짧은 기간 동안 활성 스레드 수가 동시성 값을 초과할 수 있습니다. 그러나 시스템은 활성 스레드 수가 동시성 값 아래로 떨어질 때까지 새 활성 스레드를 허용하지 않음으로써 이 수를 빠르게 줄입니다. 이는 애플리케이션이 스레드 풀에 동시성 값보다 더 많은 스레드를 만들도록 하는 이유 중 하나입니다. 스레드 풀 관리는 이 항목의 범위를 벗어나지만 시스템에 프로세서가 있는 스레드 풀에 최소 두 배 이상의 스레드가 있는 것이 좋습니다. 스레드 풀링에 대한 자세한 내용은 스레드 풀을 참조하세요.

지원되는 I/O 함수

다음 함수는 I/O 완료 포트를 사용하여 완료되는 I/O 작업을 시작하기 위해 사용할 수 있습니다. I/O 완료 포트 메커니즘을 사용하도록 설정하려면 함수를 OVERLAPPED 구조체의 인스턴스 그리고 이전에 I/O 완료 포트와 연결되었던 파일 핸들에 전달(CreateIoCompletionPort 호출을 통함)해야 합니다.

프로세스 및 스레드 정보

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus