Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
IOCP IO Completion Port
NHN NEXT
남현욱
동작 원리
IOCP의 목적
IO 작업에서 동시 수행되는 스레드 개수의 상한을 설정하자!
그래서 CPU 자원을 최대한 효율적으로 사용하자!
동작 원리
IOCP 생성
당연히 IOCP를 쓰려면 먼저 IOCP를 만들어야한다.
원형
CreateIoCompletionPort(
HANDLE fileHandle, 		 	 // IOCP와 연결할 핸들. 첫 생성시는
						 		 // INVALID_HANDLE_VALUE 넘김
HANDLE ExistingCompletionPort, //IOCP 핸들. 역시 첫 생성시는 NULL.
ULONG_PTR CompletionKey, 		 //IO 완료시 넘어갈 값. 사용자가 넘기고 싶은 값 넘김.
DWORD NumberOfConcurrentThreads //한 번에 동작할 수 있는 최대 스레드 개수.
									 //0 넘기면 프로세서 숫자로 자동 지정됨.
동작 원리
IO 장치와 IOCP 연결 - 마찬가지로 CreateIoCompletionPort를 쓴다.
HANDLE
HANDLE hPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
HANDLE port = CreateIoCompletionPort(socket,hPort,(ULONG_PTR)id,0);
//error
if(port != hPort) return false;
장치 리스트
새로운 레코드
추가
hDevice CompletionKey
레코드 추가 시점
CreateIoCompletionPort 함수로 장치 연결시
레코드 삭제 시점
해당 장치의 핸들이 닫혔을 때
CreateIoCompletionPort를 호출하
면 내부적으로 IOCP와 연결된 여러 장
치들을 관리하기 위한 장치 리스트에 새
로운 레코드를 추가한다.
동작 원리
장치와 연결이 끝나면 IO 작업이 완료된 후 완료된 IO에 대한 처리를 수행
할 스레드 풀을 구성한다. 일반적으로 스레드 풀의 크기는 프로세서 개수의
2배 정도를 할당한다(이유는 이후 구조 설명 후 다시 살펴보자).
for(int i = 0; i < ioThreadCount; i++)
{
	 DWORD ThreadId;
	 HANDLE hThread = (HANDLE)_beginthreadex(NULL,0, workerThread,
									(LPVOID)i,0,(unsinged int)&dwThreadId);
}
동작 원리
IO Completion Queue
dwNumberOfBytes
Transferred
dwCompletionKey lpOverlapped dwError
레코드 추가 시점
I/O 요청이 완료되었을 때
PostQueuedCompletionStatus 함수 호출시
레코드 삭제 시점
IO completion Port가 Wait Thread Queue의 항목을 가져올 때
즉 이 큐는 IOCP와 연결한 device의 IO 작업이 끝났음을 알려주는 큐이다.
방금 전 생성한 스레드 풀에서 적절한 스레드들이 IO Completion Queue
에서 작업거리를 꺼내 IO 완료에 따른 처리를 수행하게 된다.
동작 원리
Waiting Thread Queue(*LIFO)
dwThreadId
레코드 추가 시점
GetQueuedCompletionStatus 함수가 호출되었을 때
레코드 삭제 시점
IO Completion Queue가 비어있지 않고 수행 중인 스레드의 개수가 동시 수행 가능한 스레드 개수를 초과하지 않은 경우
이 큐는 IO 작업의 완료를 처리하기 위해 대기하고 있는 스레드들의
큐이다. GetQueuedCompletionStatus 함수를 호출했을 때 당장 IO
Completion Queue에 항목이 없거나 동시 수행 가능한 스레드 개수를
초과한 경우 대기 큐에 들어가 대기하고 있다가 적당한 상황이 오면
깨어나서 IO 완료에 대한 처리를 수행하게 된다.
동작 원리
Release Thread List
dwThreadId
레코드 추가 시점
IO Completion Port가 Waiting Thread Queue에 있는 스레드를 깨우는 경우
일시정지되었던 스레드가 다시 깨어났을 경우
레코드 삭제 시점
스레드가 다시 GetQueuedCompletionStatus 함수를 호출했을 때
스레드가 정지되는 함수를 호출했을 때
실제 IO 완료에 따른 처리를 수행하기 위해 깨어나서 돌고 있는 Thread
들의 리스트이다. IOCP에서 지정한 개수만큼의 스레드만 이 리스트에 속할
수 있다(그 만큼만 깨어있을 수 있다).
동작 원리
Paused Thread List
dwThreadId
레코드 추가 시점
수행 중이던 스레드가 스레드의 정지시키는 함수를 호출했을 때
레코드 삭제 시점
일시정지되었던 스레드가 다시 깨어날 경우
작업을 처리하던 스레드가 특정 함수의 호출(스레드를 블락시키는 함수 등)
했을 때 이 스레드를 Paused Thread List로 보낸다.
예시
장치 리스트
socket
프로세서가 2개인 컴퓨터에서 socket과 연결된 IOCP가 하나 있
다고 가정할 때 IOCP의 동작을 통해 구조를 이해해보자.
예시
장치 리스트
socket
아직 완료된 IO 작업이 없으므로 IO Completion Queue는 비
어있는 상태일 것이다.
IO Completion Queue
예시
장치 리스트
socket
Waiting Thread Queue에는 처음 생성한 스레드 풀에서
GetQueuedCompletionStatus 함수 호출을 통해 들어간 4개
의 스레드가 대기중인 상황이다.
IO Completion Queue
Waiting Thread Queue
Thread 1 Thread 2 Thread 3 Thread 4
예시
장치 리스트
socket
아직 완료된 IO 작업이 없으니 Release Thread List와 Pause
Thread List도 비어있는 상태.
IO Completion Queue
Waiting Thread Queue
Thread 1 Thread 2 Thread 3 Thread 4
Release Thread List
Pause Thread List
예시
장치 리스트
socket
이 때 2개의 IO 작업이 완료되었다고 하자.
IO Completion Queue
Waiting Thread Queue
Thread 1 Thread 2 Thread 3 Thread 4
Release Thread List
Pause Thread List
Completion 1 Completion 2
예시
장치 리스트
socket
IO Completion을 처리하기 위해 Waiting Thread Queue
에 가장 최근에 들어온 스레드 2개를 깨우게 된다(Waiting
Thread Queue는 성능을 위해 LIFO 순으로 Thread를 꺼낸
다).
IO Completion Queue
Waiting Thread Queue
Thread 1 Thread 2 Thread 3 Thread 4
Release Thread List
Pause Thread List
Thread 4 Thread 3
예시
장치 리스트
socket
Release Thread List에 2개가 돌고 있을 때, 또 다시 새로운 IO
작업이 완료되었다고 하자. 이 때 이미 Relase Thread List에 2
개의 Thread가 돌고 있기 때문에 새로운 스레드는 깨우지 않는
다.
IO Completion Queue
Waiting Thread Queue
Thread 1 Thread 2
Release Thread List
Pause Thread List
Thread 4 Thread 3
Completion 3 Completion4
예시
장치 리스트
socket
이 때 Thread4에서 어떤 함수가 호출되어 이 Thread가 대기 상
태에 빠지게 된다면 어떻게 될까?
IO Completion Queue
Waiting Thread Queue
Thread 1 Thread 2
Release Thread List
Pause Thread List
Thread 4 Thread 3
Completion 3 Completion4
Thread 4
예시
장치 리스트
socket
IOCP는 항상 스레드 개수를 사용할 수 있는 한 많이 사용하려
고 한다. 그래서 이 경우 1개만 돌고 있으므로 Waiting Thread
Queue에서 새로운 스레드를 꺼낸다(이 때문에 프로세서 개수
보다 많은 스레드가 필요한 것이다!).
IO Completion Queue
Waiting Thread Queue
Thread 1 Thread 2
Release Thread List
Pause Thread List
Thread 3
Completion 4
Thread 4
Thread 2
예시
장치 리스트
socket
그런데 이 상태에서 Pause Thread List의 스레드가 대기 상태
의 작업이 끝난다면? 이 걸 그냥 깨우면 Release Thread List의
최대 Thread 개수를 초과하게 되어버린다.
IO Completion Queue
Waiting Thread Queue
Thread 1
Release Thread List
Pause Thread List
Thread 3
Completion 4
Thread 4
Thread 2
이제 대기 상태 끝!
예시
장치 리스트
socket
그래서 이 경우 IOCP는 Release Thread List의 스레드가 다시
대기 상태에 들어가기 전까진 스레드를 깨우지 않는다.
IO Completion Queue
Waiting Thread Queue
Thread 1
Release Thread List
Pause Thread List
Thread 3
Completion 4
Thread 4
Thread 2
불가능!
코드(GQCS/PQCS)
각각의 IO Worker Thread에서는 아래와 같은 코드를 수행해서 IO 완료에 따른 처리를 하게 된다.
while(true)
{
	 int ret = GetQueuedCompletionStatus(port,&numBytes,&completionKey,&overlapped,TIME_OUT);
	
	 if(ret == 0 && GetLastError() == WAIT_TIMEOUT)
		continue;	
	//그 외 경우 에러 처리는 생략. overlapped가 nullptr이라든가 numBytes가 이상하다든가 하는 경우들이 있음. 보통 연결 종료.
	//completionKey가 종료된 IO작업 종류를 담고 있다고 가정
	 if(completionKey == IO_RECV)
	{
		//recv 관련 작업 수행
	}
	 else if(completionKey == IO_SEND)
	{
		//send 관련 작업 수행
	}
}
PQCS는 IO Completion Queue에 새 요소를 집어넣을 수 있게 해준다. 다른 스레드에 뭔가 완료
통지를 보내고 싶을 때 유용. 인자는 GQCS와 거의 동일(GQCS 호출시 필요한 데이터 거의 그대로
넘김)
//example. 대기중인 다른 스레드 종료시키기(THREAD_RELEASE가 스레드 종료 이벤트라고 가정).
PostQueuedCompletionStatus(port, 100, THREAD_RELEASE , overlapped);
코드(SEND/RECV)
send, recv를 호출하는 부분에서는 아래와 같은 방식으로 코드를 작성하게 된다.
WSARecv(clntSock,			//클라이언트 소켓.
	&ioContext->wsaBuf,		//읽을 데이터 버퍼의 포인터.
	1,					//데이터 입력 버퍼의 개수.
	nullptr,				//recv 결과 읽은 바이트 수. IOCP에서는 비동기 방식으로 사용하지 않으므로 nullptr 넘겨도 무방.
	 &flags,	 	 	 	 //recv에 사용될 플래그.
	 &ioContext->overlapped),	//OVERLAPPED 구조체의 포인터
	nullptr);				//completion routine 함수의 포인터. IOCP에서는 사용하지 않으므로 nullptr 넘겨도 무방.
WSARecv(clntSock,
	&ioContext->wsaBuf,
	1,
	nullptr,
	0,
	&ioContext->overlapped),
	nullptr);
recv나 send나 거의 똑같다. io 작업을 위한 버퍼와 OVERLAPPED 구조체의 포인터를 넘겨준다.

More Related Content

Iocp 기본 구조 이해

  • 1. IOCP IO Completion Port NHN NEXT 남현욱
  • 2. 동작 원리 IOCP의 목적 IO 작업에서 동시 수행되는 스레드 개수의 상한을 설정하자! 그래서 CPU 자원을 최대한 효율적으로 사용하자!
  • 3. 동작 원리 IOCP 생성 당연히 IOCP를 쓰려면 먼저 IOCP를 만들어야한다. 원형 CreateIoCompletionPort( HANDLE fileHandle, // IOCP와 연결할 핸들. 첫 생성시는 // INVALID_HANDLE_VALUE 넘김 HANDLE ExistingCompletionPort, //IOCP 핸들. 역시 첫 생성시는 NULL. ULONG_PTR CompletionKey, //IO 완료시 넘어갈 값. 사용자가 넘기고 싶은 값 넘김. DWORD NumberOfConcurrentThreads //한 번에 동작할 수 있는 최대 스레드 개수. //0 넘기면 프로세서 숫자로 자동 지정됨.
  • 4. 동작 원리 IO 장치와 IOCP 연결 - 마찬가지로 CreateIoCompletionPort를 쓴다. HANDLE HANDLE hPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0); HANDLE port = CreateIoCompletionPort(socket,hPort,(ULONG_PTR)id,0); //error if(port != hPort) return false; 장치 리스트 새로운 레코드 추가 hDevice CompletionKey 레코드 추가 시점 CreateIoCompletionPort 함수로 장치 연결시 레코드 삭제 시점 해당 장치의 핸들이 닫혔을 때 CreateIoCompletionPort를 호출하 면 내부적으로 IOCP와 연결된 여러 장 치들을 관리하기 위한 장치 리스트에 새 로운 레코드를 추가한다.
  • 5. 동작 원리 장치와 연결이 끝나면 IO 작업이 완료된 후 완료된 IO에 대한 처리를 수행 할 스레드 풀을 구성한다. 일반적으로 스레드 풀의 크기는 프로세서 개수의 2배 정도를 할당한다(이유는 이후 구조 설명 후 다시 살펴보자). for(int i = 0; i < ioThreadCount; i++) { DWORD ThreadId; HANDLE hThread = (HANDLE)_beginthreadex(NULL,0, workerThread, (LPVOID)i,0,(unsinged int)&dwThreadId); }
  • 6. 동작 원리 IO Completion Queue dwNumberOfBytes Transferred dwCompletionKey lpOverlapped dwError 레코드 추가 시점 I/O 요청이 완료되었을 때 PostQueuedCompletionStatus 함수 호출시 레코드 삭제 시점 IO completion Port가 Wait Thread Queue의 항목을 가져올 때 즉 이 큐는 IOCP와 연결한 device의 IO 작업이 끝났음을 알려주는 큐이다. 방금 전 생성한 스레드 풀에서 적절한 스레드들이 IO Completion Queue 에서 작업거리를 꺼내 IO 완료에 따른 처리를 수행하게 된다.
  • 7. 동작 원리 Waiting Thread Queue(*LIFO) dwThreadId 레코드 추가 시점 GetQueuedCompletionStatus 함수가 호출되었을 때 레코드 삭제 시점 IO Completion Queue가 비어있지 않고 수행 중인 스레드의 개수가 동시 수행 가능한 스레드 개수를 초과하지 않은 경우 이 큐는 IO 작업의 완료를 처리하기 위해 대기하고 있는 스레드들의 큐이다. GetQueuedCompletionStatus 함수를 호출했을 때 당장 IO Completion Queue에 항목이 없거나 동시 수행 가능한 스레드 개수를 초과한 경우 대기 큐에 들어가 대기하고 있다가 적당한 상황이 오면 깨어나서 IO 완료에 대한 처리를 수행하게 된다.
  • 8. 동작 원리 Release Thread List dwThreadId 레코드 추가 시점 IO Completion Port가 Waiting Thread Queue에 있는 스레드를 깨우는 경우 일시정지되었던 스레드가 다시 깨어났을 경우 레코드 삭제 시점 스레드가 다시 GetQueuedCompletionStatus 함수를 호출했을 때 스레드가 정지되는 함수를 호출했을 때 실제 IO 완료에 따른 처리를 수행하기 위해 깨어나서 돌고 있는 Thread 들의 리스트이다. IOCP에서 지정한 개수만큼의 스레드만 이 리스트에 속할 수 있다(그 만큼만 깨어있을 수 있다).
  • 9. 동작 원리 Paused Thread List dwThreadId 레코드 추가 시점 수행 중이던 스레드가 스레드의 정지시키는 함수를 호출했을 때 레코드 삭제 시점 일시정지되었던 스레드가 다시 깨어날 경우 작업을 처리하던 스레드가 특정 함수의 호출(스레드를 블락시키는 함수 등) 했을 때 이 스레드를 Paused Thread List로 보낸다.
  • 10. 예시 장치 리스트 socket 프로세서가 2개인 컴퓨터에서 socket과 연결된 IOCP가 하나 있 다고 가정할 때 IOCP의 동작을 통해 구조를 이해해보자.
  • 11. 예시 장치 리스트 socket 아직 완료된 IO 작업이 없으므로 IO Completion Queue는 비 어있는 상태일 것이다. IO Completion Queue
  • 12. 예시 장치 리스트 socket Waiting Thread Queue에는 처음 생성한 스레드 풀에서 GetQueuedCompletionStatus 함수 호출을 통해 들어간 4개 의 스레드가 대기중인 상황이다. IO Completion Queue Waiting Thread Queue Thread 1 Thread 2 Thread 3 Thread 4
  • 13. 예시 장치 리스트 socket 아직 완료된 IO 작업이 없으니 Release Thread List와 Pause Thread List도 비어있는 상태. IO Completion Queue Waiting Thread Queue Thread 1 Thread 2 Thread 3 Thread 4 Release Thread List Pause Thread List
  • 14. 예시 장치 리스트 socket 이 때 2개의 IO 작업이 완료되었다고 하자. IO Completion Queue Waiting Thread Queue Thread 1 Thread 2 Thread 3 Thread 4 Release Thread List Pause Thread List Completion 1 Completion 2
  • 15. 예시 장치 리스트 socket IO Completion을 처리하기 위해 Waiting Thread Queue 에 가장 최근에 들어온 스레드 2개를 깨우게 된다(Waiting Thread Queue는 성능을 위해 LIFO 순으로 Thread를 꺼낸 다). IO Completion Queue Waiting Thread Queue Thread 1 Thread 2 Thread 3 Thread 4 Release Thread List Pause Thread List Thread 4 Thread 3
  • 16. 예시 장치 리스트 socket Release Thread List에 2개가 돌고 있을 때, 또 다시 새로운 IO 작업이 완료되었다고 하자. 이 때 이미 Relase Thread List에 2 개의 Thread가 돌고 있기 때문에 새로운 스레드는 깨우지 않는 다. IO Completion Queue Waiting Thread Queue Thread 1 Thread 2 Release Thread List Pause Thread List Thread 4 Thread 3 Completion 3 Completion4
  • 17. 예시 장치 리스트 socket 이 때 Thread4에서 어떤 함수가 호출되어 이 Thread가 대기 상 태에 빠지게 된다면 어떻게 될까? IO Completion Queue Waiting Thread Queue Thread 1 Thread 2 Release Thread List Pause Thread List Thread 4 Thread 3 Completion 3 Completion4 Thread 4
  • 18. 예시 장치 리스트 socket IOCP는 항상 스레드 개수를 사용할 수 있는 한 많이 사용하려 고 한다. 그래서 이 경우 1개만 돌고 있으므로 Waiting Thread Queue에서 새로운 스레드를 꺼낸다(이 때문에 프로세서 개수 보다 많은 스레드가 필요한 것이다!). IO Completion Queue Waiting Thread Queue Thread 1 Thread 2 Release Thread List Pause Thread List Thread 3 Completion 4 Thread 4 Thread 2
  • 19. 예시 장치 리스트 socket 그런데 이 상태에서 Pause Thread List의 스레드가 대기 상태 의 작업이 끝난다면? 이 걸 그냥 깨우면 Release Thread List의 최대 Thread 개수를 초과하게 되어버린다. IO Completion Queue Waiting Thread Queue Thread 1 Release Thread List Pause Thread List Thread 3 Completion 4 Thread 4 Thread 2 이제 대기 상태 끝!
  • 20. 예시 장치 리스트 socket 그래서 이 경우 IOCP는 Release Thread List의 스레드가 다시 대기 상태에 들어가기 전까진 스레드를 깨우지 않는다. IO Completion Queue Waiting Thread Queue Thread 1 Release Thread List Pause Thread List Thread 3 Completion 4 Thread 4 Thread 2 불가능!
  • 21. 코드(GQCS/PQCS) 각각의 IO Worker Thread에서는 아래와 같은 코드를 수행해서 IO 완료에 따른 처리를 하게 된다. while(true) { int ret = GetQueuedCompletionStatus(port,&numBytes,&completionKey,&overlapped,TIME_OUT); if(ret == 0 && GetLastError() == WAIT_TIMEOUT) continue; //그 외 경우 에러 처리는 생략. overlapped가 nullptr이라든가 numBytes가 이상하다든가 하는 경우들이 있음. 보통 연결 종료. //completionKey가 종료된 IO작업 종류를 담고 있다고 가정 if(completionKey == IO_RECV) { //recv 관련 작업 수행 } else if(completionKey == IO_SEND) { //send 관련 작업 수행 } } PQCS는 IO Completion Queue에 새 요소를 집어넣을 수 있게 해준다. 다른 스레드에 뭔가 완료 통지를 보내고 싶을 때 유용. 인자는 GQCS와 거의 동일(GQCS 호출시 필요한 데이터 거의 그대로 넘김) //example. 대기중인 다른 스레드 종료시키기(THREAD_RELEASE가 스레드 종료 이벤트라고 가정). PostQueuedCompletionStatus(port, 100, THREAD_RELEASE , overlapped);
  • 22. 코드(SEND/RECV) send, recv를 호출하는 부분에서는 아래와 같은 방식으로 코드를 작성하게 된다. WSARecv(clntSock, //클라이언트 소켓. &ioContext->wsaBuf, //읽을 데이터 버퍼의 포인터. 1, //데이터 입력 버퍼의 개수. nullptr, //recv 결과 읽은 바이트 수. IOCP에서는 비동기 방식으로 사용하지 않으므로 nullptr 넘겨도 무방. &flags, //recv에 사용될 플래그. &ioContext->overlapped), //OVERLAPPED 구조체의 포인터 nullptr); //completion routine 함수의 포인터. IOCP에서는 사용하지 않으므로 nullptr 넘겨도 무방. WSARecv(clntSock, &ioContext->wsaBuf, 1, nullptr, 0, &ioContext->overlapped), nullptr); recv나 send나 거의 똑같다. io 작업을 위한 버퍼와 OVERLAPPED 구조체의 포인터를 넘겨준다.