IOCP
는 입출력 완료 포트라는 뜻으로, socket이나 file I/O를 최소한의 스레드를 사용해서 처리하는 기법이다. 많은 async I/O 요청을 처리하는 데에 미리 할당한 스레드 풀링과 함께 이 IOCP를 사용함으로 고성능의 입출력 처리를 가능하게 한다.
Overlapped I/O가 완료 시 이를 감지해서 사용자에게 알려주는 역할을 한다.
선행되어야 할 개념으로 Overlapped I/O (중첩 입출력 모델)과 Thread Pooling이 존재한다.
중첩 입출력 모델은 많은 입출력을 비동기로 효율적으로 처리하기 위한 고성능 I/O 방식이다.
cf. synchronous 하게 실행되는 I/O 작업
ReadFile()
메서드를 호출한다면, read 작업을 동기적으로 실행하여 입력이 끝날 때까지 함수는 값을 반환하지 않고 Blocking되어 대기한다.
synchronous I/O에서의 스레드는 I/O 작업을 시작하고 I/O 요청이 완료될 때까지 즉시 대기 상태로 돌입하는 방식으로 blocking
으로 대기한다. 반면 overlapped I/O는 IO 작업을 대기하지 않고 비동기적으로 처리하는 방식이다.
풀(Pool)이란 집합소 또는 수영장에서 물을 모아놓은곳등의 의미. 즉 어떤 것을 모아 놓은 곳을 말한다.
Thread Pool은 마치 게임 클라이언트에서의 오브젝트 풀링 기법처럼, 여러개의 쓰레드를 대기상태로 미리 생성해놓은 풀을 이용하는 기법이다.
이 Thread Pool을 이용하는 핵심은 풀 안에서 대기중인 쓰레드에 대해 필요한만큼 실행 상태로 바꾸어 사용하고, 작업이 완료된 스레드는 파괴하지 않고 대기 상태로 바꿔 풀로 회수하는 일련의 과정이다.
쓰레드 풀링을 사용하는 이유는, 오브젝트 풀링과 마찬가지로 CPU에 무리가 가는 잦은 생성과 파괴 작업을 피하는 대신 상태만 활성화/비활성화함으로 오버헤드를 줄일 수 있기 때문이다. 쓰레드 간 작업 전환 시의 발생하는 Context Switching에 대해서도 풀의 쓰레드를 상황에 맞춰 조절함으로 회피가능하다.
다시 IOCP에 대해 이야기를 하자면,
많은 동시 비동기 I/O 요청을 처리하는 프로세스는 미리 할당된 스레드 풀
과 함께 IOCP
를 사용함으로써 I/O 요청을 받았을 때 스레드를 만드는 것보다 더 빠르고 효율적으로 처리할 수 있다.
CreateIoCompletionPort 함수
는 IOCP를 생성하고, 하나 이상의 파일 핸들을 해당 포트와 연결한다. 이러한 파일 핸들 중 하나에 대한 async I/O 작업이 완료되면 I/O completion 패킷이 연결된 IOCP에 대한 FIFO로 Queue에 대기한다.
파일 핸들이 완료 포트와 연결된 후, 완료 패킷이 큐에서 제거될 때까지 상태 블록이 업데이트되지 않는다.
(예외: 동기적 오류 반환)
스레드는 GetQueuedCompletionStatus 함수
를 사용하여 완료 패킷이 IOCP 큐에 대기할 때까지 기다린다.
IOCP에서 차단된 스레드는 LIFO 순서로 해제되며, 다음 완료 패킷은 해당 스레드의 FIFO 큐에서 처리된다. 실행 중인 스레드 수가 설정된 동시성 값
보다 작으면 대기 중인 스레드가 완료 패킷을 처리한다.
스레드는 PostQueuedCompletionStatus 함수
를 사용하여 큐에 완료 패킷을 추가할 수 있으며, 이는 I/O 시스템 외부에서도 통신을 받을 수 있게 한다.
IOCP와 연결된 모든 핸들은 올바르게 닫혀야 한다. CloseHandle 함수
를 통해 IOCP 핸들을 닫아 시스템 리소스를 해제한다.