Overlapped IO Model2

2022. 2. 15. 01:24게임서버/win socket 프로그래밍

320x100

안녕하세요 대학생 개발자입니다.

 

저번 글에서는 Overlapped IO Model1에 대한 내용에 대해서 언급했었습니다.

Overlapped IO Model1에서는 Event를 이용해서 IO에 대한 완료통지를 하는것이라고 했었고,

그렇기 때문에 생기는 비효율적인 면에 대해서 언급했었습니다.

 

이번글에서 다룰 내용은 Overlapped IO Model2번째 모델입니다.

아마 이전글에서 언급을 했었겠지만 Overlapped IO Model2의 경우에는 Completion_Routine이라는 CALLBACK 등록을 통해서 IO의 완료 통지를 받는다고 했었습니다.

 

그럼 우선 우리가 알아야 하는건 CALLBACK이 과연 뭐냐입니다.

CALLBACK 많은 사람들이 말을하고 있지만 이게 정확히 뭐냐 라고 물어봤을 때 정확히 대답하는 사람을 본 경험이 없었습니다.

물론 필자도 마찬가지로 정확한 설명이 힘듭니다.

 

CALLBACK이란건 누군가가(?) 어떠한 함수호출을 대신 해주는 경우를 의미할겁니다.

그리고 제가 이때까지 글에서 언급한 함수를 수행할 수 있는 단위는 스레드밖에없습니다.

즉 스레드가 실행한다는 말이죠.

우선 콜백을 수행할 수 있는 대상부터 봐야할 것 같습니다.

 

콜백은 누가 호출하는가입니다.

당연히 콜백은 지금 이 콜백함수가 존재하는 프로세스의 어떠한 스레드가 수행을 할겁니다.

다른 프로세스에서 우리 코드를 수행한다는것 자체는 절대로 말이 안되기 때문입니다.

여기서 고민거리는 내가 짠 코드를 남이 호출 할 수있는가에 대한 부분입니다.

남이 호출한다고하면 내가 짜놓은 코드를 다른 스레드가 작동을 시킨다라는 경우가 될겁니다.

다음 맥락에서 알아보도록 하겠습니다.

 

일단 확실한건 콜백을 등록하는건 분명 내가 짠 코드(스레드)에서 수행할겁니다.

 

여기서 만약 다른 스레드가 내가 등록한 콜백을 수행한다고 한다면 어떤일이 일어날까요...

뭐 일단 기분이 조금 찝찝할겁니다.

누가만든건지도 모르는 스레드가 내 스레드에서 등록한 콜백을 수행한다는 그 느낌자체가 별로 좋지 않습니다.

그리고 문제는 이뿐만이 아닙니다.

만약 콜백을 우리 스레드에서 수행하지 않으면 추가적인 동기화 작업이 들어가야될 것같습니다.

 

아마 WinAPI같은것을 사용해보신 분들이라면 알겁니다.

우리는 WndProc를 등록하고 가만히 있으면 이벤트가 생길때마다 갑자기 호출을 해줍니다.

이건 누가 호출해주는거죠?

이건 메인함수에 있는 DispatchMessage라는 함수가 WndProc가 작동할 수 있게 작업을 포스팅을 해주는겁니다.

즉 우리코드에서 Callback을 유도한 상황이 될 수 있겠죠.

 

여기서 제가 하려는 말은 콜백에 대한 유도나 그에대한 실행또한 다 우리가 직접 해야되는게 된다는 의미입니다.

 

콜백은 어디서 갑자기 뚝하고 튀어나오는것이 아니라는 의미가 되겠죠

 

뭐... 여기까지는 대충 겉면적으로 드러나는 불쾌한 점들이었다면 지금부터는 Overlapped IO Model2의 구현 방식때문에 불가능하다는 말씀을 드리려고합니다.

 

우선 APC라는것에대한 언급이좀 있어야되겠습니다.

APC라는것은 비동기 프로시져 콜입니다.

 

그리고 이 APC큐라는것은 각 쓰레드마다 하나씩 존재하고있구요.

IO가 완료가되면 이 APC큐에 큐잉을 해주는 방식으로 IO완료를 통보하고 있습니다.

그리고 IO를 요청한 스레드는 스스로 Alertable Wait상태가되어 APC큐에서 하나의 완료루틴을 디큐잉 해서 작업을 처리하는 방식으로 완료에 대한 처리작업을 할것입니다.

자세한 정보에 대해서는 microsoft에서 제공하는 APC를 보시면 될 것 같습니다. 

_APC_

그리고 이 모델에서 IO는 각 스레드에 종속적입니다.

동기의 경우에는 당연하게도 IO가 끝날때까지 블로킹에 걸리게되니 종속적이라고 할 수 밖에 없을 것 같습니다.

비동기의 경우에도 다르진 않습니다.

커널에게 IO를 부탁하는거구요, 커널은 IO요청을 부탁받은 스레드의 APC큐에 넣어주게 됩니다.

 

여기서 의문이 하나 들수있습니다.

꼭 IO를 요청한 스레드의 APC큐에만 넣어줘야하는가입니다.

뭐... 이건 IOCP에대한 글을 작성할때 제 추론에대해서 정리를 하겠습니다.

공식적인 문서를 보고 적은건 아니니 틀릴수도 있다는점을 알아주시길 바랍니다.

 

비동기 입출력을 요청하면 그 요청한 스레드의 APC큐에 CompletionRoutine을 큐잉을 해주기 때문에 나중에 언급드리겠지만 이부분이 Overlapped IO Model2의 치명적인 단점이 될 수 있습니다.

 

우선 그림을 보면서 순서를 좀 살펴보겠습니다.

위 실행 순서에서 우리의 스레드는 비동기 IO를 걸어두고 alertable wait상태로 전환되어야 됩니다.

뭐... alertable wait으로 진입을 하게 해주는 함수는 SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx또는 WaitForSingleObjectEx등 다양한 함수가 있습니다.

(이번 글에서는 API에 대해서는 크게 다루지 않을예정입니다.)

alertable wait상태가 된다면 APC queue에 CompletionRoutine이 들어오기 전까지 스레드가 block에 걸려있을겁니다.

그럼 커널에서는 비동기 IO가 완료가 된다면 APC큐라는 곳에 CompletionRoutine을 넣게됩니다.

APC큐에 완료된 IO가 있다는 완료루틴이 들어왔다면 block상태이던 alertable wait상태의 스레드가 running 상태가 되어 콜백을 호출합니다.

그리고 콜백이 완료가되면 alertable wait을 탈출해서 다시 스레드가 원래 하던 작업으로 돌아가게될 것입니다.

음... 여기서 alertable wait상태와 스레드의 running block상태를 헷갈리시는 분들이 간혹 있으십니다만,

alertable wait상태라고해서 꼭 blocking은 아닙니다.

CompletionRoutine이 들어와서 실행하게 되었다면 blocking상태에서는 깨어나서 running상태가 되지만 여전히 alertable wait상태에 있는겁니다.

CompletionRoutine이 완료가 되고 리턴이 되었을때, 그때 alertable wait상태가 풀리게 됩니다.

순서는 대략 위와 같이 이렇게 되어있습니다.

 

그리고 아까 말씀드렸던 치명적인 단점부분에 대한 내용은 지금부터 말씀을 드리겠습니다.

내용에서 읽으셨듯 APC큐는 하나의 스레드마다 하나씩 가지고있는것이구요.

IO는 스레드에 종속적입니다.

여기부터는 추론의 부분입니다.

추론이 가능하시겠지만 다른스레드는 지금 스레드가 IO를 하고있는지 조차 모르고있습니다.

그렇기 때문에 APC큐에 들어온 내용은 무조건 비동기 IO를 요청한 스레드에서 처리를 해야된다는 점인데요.

결론으로 만약 하나의 세션에 대한 비동기 IO를 처리하게된 스레드에서는 그 연결이 사라지기 전까지 계속 그 세션을 담당해야 될겁니다.

 

즉 이말은 무슨말이냐... 스레드에 업무 분산이 제대로 되지 않을 가능성이 있다는 의미입니다.

왜냐하면 현재 Overlapped IO Model2에서는  각 스레드별로 APC큐를 가지고 있기 때문이고 그를 공유할 수 있는 방법이 없기 때문입니다.

하나의 스레드는 죽어나라 열심히 일하고 있는데 옆에 스레드는 놀면서 아무것도 안하고 있어도 상관이 없다는 의미가됩니다.

 

다른부분은 다 차치하고 보더라도 이 한가지 부분때문에 멀티스레딩에서 상당히 비효율적인 부분이 생길겁니다.

만약 내가 루틴을 처리하고 있을 때 다른 스레드가 내 큐에있는 밀린 루틴을 처리한다면 쌓여있는 콜백루틴이 상당히 빨리 처리되는 효율적인 모습을 보일겁니다...

하지만 APC큐와 IO가 하나의 스레드에 종속적인 모습을 보이다보니 이런 문제가 발생하게됩니다.

이러한 문제는 IOCP를 통해서 개선이 되긴합니다.

 

하지만 model2의 경우에는 model1의 이벤트보다 확실히 좋아진점은 있습니다.

이벤트의 경우에는 진짜 그 이벤트가 발생했다는 정보외에는 아무런 정보도 알수없기 때문에 모든 세션의 이벤트를 다 훑으면서 확인을해야 되는 단점도 있었습니다.

추가로 GetOverlappedResult함수를 호출해서 받은 byte수와 작업을 직접 받아와야 했었습니다.

하지만 모델2에서는 CALLBACK함수에 io에대한 바이트수와 작업에 대한 포인터가 들어오게 됩니다.

void __stdcall CompletionRoutine(DWORD dwError, DWORD cbTrans, LPOVERLAPPED lpOver, DWORD dwFlag)

completion routine에 대한 설명이 늦었습니다.

완료 루틴은 다음과같이 생겼습니다.

 

그리고 이벤트와 다르게 64개의 제한도 사라졌구요.

확실히 이부분은 많이 개선되었습니다만...

멀티스레딩이라는 점을 고려해놓고 본다면 위의 문제는 여간 심각한 문제가 아닐수가 없습니다.

 

그리고 APC의 경우에는 유저모드와 커널모드가 존재합니다.

그러므로 특별한 콜백을 전달해서 종료루틴을 만들수도있는것이고, 그외 특정 작업들을 포스팅할수있겠죠.

뭐... 이건 이벤트와 약간 비슷한 성질을 가진부분인 것 같습니다.

로직을 함께 전달하느냐 아니냐의 차이인것 같습니다.

 

자... Overlapped IO Model2에 대한 얼추 정리가 완료되었습니다.

그럼 저도 IO가 완료되었으니 등록된 콜백인 Sleep()를 하로가겠습니다...

다들 긴 글 읽어주셔서 감사드립니다.

그럼 안녕히계세요.

320x100

'게임서버 > win socket 프로그래밍' 카테고리의 다른 글

IOCP + Overlapped IO는 비동기IO인가  (0) 2022.02.17
IO Completion Port Introduction  (0) 2022.02.15
Overlapped IO Model + heap  (0) 2022.02.11
Overlapped IO Introduction  (0) 2022.02.10
L4와 L7그리고 서버  (0) 2021.11.04