Thread Design3과 잡다한얘기

2022. 2. 9. 23:25게임서버/멀티쓰레드 이론

320x100

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

저번 글에서는 IO를 완료한 스레드가 직접 로직에 개입을하는 그런 스레드 디자인을 알아봤습니다.

그리고 IO스레드와 따로 메시지를 처리하는 스레드와 월드의 프레임을 돌려주는 스레드를 분리한 디자인과의 차이점도 확인을 했었습니다.

그리고 가장 중요한 어떤 디자인이 더 효율적이고 더 좋은 디자인인지에 대한 논의를 해봤었습니다.

 

이번글에서는 헤테로 지니어스와 호모 지니어스에 대한 얘기를 해볼겁니다.

물론 이얘기는 진작 언급 했어야 됬지만 이때까지의 구조는 거의 모두 호모지니어스에 가까운 구조였습니다.

 

먼저 그럼 시작에 앞서서 이부분에 대해서 짚고 넘어가겠습니다.

워커 스레드들이 있을 때 우리는 이 워커 스레드들이 어떠한 작업을 할지 알려줘야 됩니다.

하나의 스레드는 하나의 함수단위 일거리를 가지고 있어야 되기 때문이죠.

이 경우 각 스레드들의 업무가 동일하다면 이건 호모 지니어스 구조일겁니다.

그리고 각각의 스레드가 하는 업무가 다르다면 그 경우에는 헤테로 지니어스에 해당하는 경우일겁니다.

 

설명이 조금 간략했습니다.

간단한 예시를 하나 들어보겠는데요.

사람이 10명이 있고 우리는 이사람들이 청소를 하길 원합니다.

우리는 이런식으로 지시를 할 수 있겠죠.

모두에게 빗자루와 쓰레받이를 주면서 다들 바닥을 쓸어주세요.

와같은 지시를 할 수 있을겁니다.

이 경우에는 호모지니어스에 해당합니다.

모두가 똑같은 것을 수행하거든요.

하지만 1명에게는 빗자루를 주며 모든 쓰레기를 쓸어달라고하고 1명에게는 걸레를 주어서 모든곳을 다 닦게하고 어떤 사람에게는 행주를 주며 창문을 닦으라고 하고... 이런식으로 업무가 모두 분배가 된다면 이경우에는 헤테로지니어스에 해당할 겁니다.

 

아마 하드웨어에 관심이 있으신 분이라면 이런 얘기를 다들 들어보셨을 거에요

cpu를 설계할때 고효율코어와 고성능코어를 담을건데요...

이 경우 고효율과 고성능은 헤테로 구조가 되는겁니다.

반대로 고효율4코어 고성능 8코어등 이런건 헤테로 지니어스구조 중에서도,

각각의 코어에 대한 경우에는 호모지니어스 구조가 되는겁니다.

뭐... 인텔의 P코어 + E코어나, 애플 M1의 파이어스톰 + 아이스스톰등이 이에 해당할겁니다.

 

그리고 우리가 이때까지 봐왔던 구조들은 이렇습니다.

싱글스레드로 서버가 하나 도는 구조는 애초에 멀티스레드가 아니니.. 구분하지 않아야 겠습니다만.

뭐 둘중 하나로 꼭 특정 지어야 된다고한다면 헤테로지니어스에 해당할 것 같구요.

 

또 하나의 구조가 있었습니다.

IO가 끝난 스레드가 직접 메시지에 대한 처리 부분까지 담당하고 있는 스레드 설계입니다.

그리고 이 스레드들은 scale out이 가능한 구조죠.

이런식으로 동일한 업무를 모두 수행하고 있기 때문에 호모 지니어스의 특징이 잘 보이는 구조라고 볼 수있겠습니다.

 

그리고 이번에 정리를 할 구조는 이때까지 했던 구조중 가장 헤테로 구조에 가까운 방식으로 설계가 될 것입니다.

물론 IO를 처리하는 스레드의 경우는... 어쩔수없는 호모지니어스 구조를 따르게 될것이지만,

메시지를 처리하는 부분에 있어서 컨텐츠 단위로 쪼개서 스레드를 설계할 것입니다.

그렇기 때문에 헤테로 구조랑 가깝다고 말씀드린거구요

 

아마 예전에 이부분을 언급했는지는 잘 모르겠습니다만,

서버 분산을 할땐 부하가 큰 것들을 위주로 떨어내는 방식으로 분산을 했었습니다.

뭐.. 예전의 경우에는 코어 갯수가 듀얼코어 쿼드코어 이정도 선이었기 때문에 충분히 물리적인 분산을 통해서 성능 향상을 이끌어 낼 수 있었을겁니다.

하지만 지금의 경우에는 서버가 아닌 일반 소비자들이 사용하는 하이엔드급 cpu들도 거의 20코어 30코어씩 들어가는 마당이기 때문에... 더이상의 물리적인 분산은 스레드 분리에 비해 오히려 성능 향상을 가져오지 못합니다.

오히려 네트워크를 통하면서 레이턴시가 생기게되어 더 늦어지게 됩니다.

이럴 바에는 차라리 스레드르 좀더 효율적으로 잘 사용해서 코어의 성능을 쭉쭉 뽑아내보자는게 더 좋은 접근 방법인거죠.

 

일단은 이야기 하고 있었던 물리적인 분산을 하던 시기에 맞춰서 얘기를 좀더 이어가보겠습니다.

예전에 분산 서버를 채택하는 경우에는 주로 이런경우였습니다.

완전 분리되는 하나의 서브 컨텐츠이거나, 로직적인 부하가 너무 컸기 때문인데요.

전자의 경우에는 1컨텐츠 1서버가 나왔습니다.

그냥 제 필기를 그대로 가져와버렸습니다 ㅋㅋㅋㅋ...

다음과 같은 방식으로 하나의 서브 컨텐츠나 주요 로직들은 다음과 같이 분리가 가능했었습니다.

그리고 각각의 서버는 scale out을 통해서 성능 향상이 가능했었죠.(이경우에는 호모지니어스가 되겠군요)

 

아마 예전의 IO를 마친 스레드가 그 메시지에 대한 처리까지 하는 구조였다면 하나의 스레드가 이게 로비에 보내야될 것인지 아니면 필드로 가는건지를 직접 스위치문을 돌아 판단했을겁니다.

하지만 이경우에는 그냥 IO를 하는 스레드가 적당한 스레드의 큐에 집어넣어주면 그걸로 끝인거니까요

사실 이렇게 놓고보니 IO스레드는 하는게 거의없어보이죠....

IO작업이란건 대부분 블로킹을 의미하고... 심지어 IO작업또한 커널이 대신해주니까요 ㅋㅋㅋ

그래서 대부분 저런식의 IO만 처리하는 경우에는 따로 여러개를 두진 않고 한두개 정도만 둬서 처리를 한다고 들은바도 있습니다.

 

뭐... 째뜬 헤테로 구조에서 고민할 점은 지금 당장은 이게 전부다인것 같습니다.

심리스 서버에 대한 잡담을 좀 해보려고 하는데요

그림이 참 예쁘지 않습니다.

동그라미들은 각각의 월드라고 생각해주시면 되겠고 월드별로 서버하나라고 생각하시면 될 것 같습니다.

그리고 심리스이니까 당연히 중간에 맵전환이나 로딩씬같은건 존재하지 않습니다.

 

이런 경우라면 우리가 생각할 수 있는 가장 무식하고도 정직한 방법으로는 이런게 있겠죠.

특정 선을 넘는순간 원래 있던 서버와는 Disconnect해버리고 새로운 서버와 Connect해버리는 방법이요...

이렇게 구현이 되어있다면 갑자기 어느 선을 지나는 순간을 기점으로 유저들이 뚝 하고 사라지고 뚝하고 생성될겁니다.

 

당연히 어색하겠죠...

이런걸 어떻게 컨텐츠적인 방법으로 잘 커버를 하느냐에 따라서 좋은 기획으로도 볼 수 있겠습니다만,

제가 생각하는 MMO에서는 이런 상황이 나온다면 상당히 보기 안예쁠것 같습니다.

 

그럼 방법2로써 다음과 같은 방법을 접목해볼 수 있겠는데요.

A서버와 B서버의 중간쯤 어느 정도 라인에 들어가면 두 서버에 모두 connect를 해서 선을 넘기 전까지는 A서버에서 있는 플레이어지만 B서버의 정보를 ReadOnlyData로써만 사용할 수 있게 해주는겁니다.

그리고 이게 가능하려면 당연히 어느정도의 과거의 상황이 보여져도 상관 없을 정도의 컨텐츠만을 그 zone에서 제공해야 될 겁니다.

만약 실시간으로 A서버에 있는 내가B서버에 있는 어떤 유저를 공격하고 그 체력이 차감되는 시스템을 하려면 각 서버간 동기화를 걸어야 될 거기때문에... 이건 사실상 매우 힘들거라고 판단됩니다.

 

아마 대부분 게임에서 특정 선을 넘으면 갑자기 서버가 바뀌고 그런개념은 잘 없을겁니다.

아마 월드와 월드 사이에는 바다 혹은... 어떠한 커다란 장벽? 이런 것들이 있을거구요.

거길 지나는 동안에는 공격을 못하거나 혹은 어떤 행동의 제약을 받는 그런게 있을겁니다.

심리스라면요 ㅋㅋㅋ...

이렇게 안되었는데도 정말 스무스하게 잘 된다면... 대단합니다.

바로 위에서 설명드린 그 상황을 그림으로 가져와 봤습니다.

중간에 있는 선은 서버와 서버의 경계구요...

저 상황에서 왼쪽 서버에 있는 플레이어가 오른쪽 서버의 플레이어를 공격하는 순간이라고 가정하겠습니다.

그와 동시에 오른쪽 서버의 두 플레이어가 서로 상호작용을 한다면 나타날 수 있는 문제가 있습니다.

문제를 방지하기위해서는 동기화를 걸어야되겠죠.

이는 퍼포먼스 저하거나 혹은 동기화를 안건다면 큰 문제가 될 수 있겠죠.

 

유감스럽게도 이 경우는 분산서버라서 문제가 될 수 있는것 아니냐 라고 말씀하시는 분이 계실수도 있습니다만,

스레드의 경우에도 다를건 없습니다.

저 경우에서 동기화를 맞춰주려고 한다면 당연히 왼쪽 서버 스레드에서 오른쪽 서버 스레드에 락을 걸고 뭔가를 액션을 취해줘야 할겁니다.

근데 이걸 안하려고 일부로... 데이터부터 모든걸 다 분리시켜서 각 서버에 공유자원이 존재하지 않게 하는건데... 락을 걸면 이 모든게 헛짓거리가 되겠죠...

그러니까 고민을 해야될 게 많다는 겁니다.

 

뭐... 제가 생각하는 나름의 해결방법이 있긴합니다만...

이부분은 제가 맞는지도 모르겠을 뿐더러 여러분들의 생각을 제가 알고있는 틀에 박히게 하면 안되기 때문에 스스로 고민을 해보시는 쪽으로 하는게 좋을 듯합니다 허허...

 

그리고 LogicThread에서 send를 하는가인데요...

어... 아뇨 안할겁니다.

send할 건이 있다면 SendThread에 따로 데이터를 전달해주고 그 데이터를 뽑아서 처리하는 구조를 일반적으로 보고 있습니다.

 

왜냐면...

IO는 상당히 느리기 때문에 SendThread에서 처리하지 않으면 그만큼의 시간을 LogicThread에서 날려먹게 됩니다ㅠㅠ...

이 과정이 0.1ms라고 하더라도 우리는 이 과정을 아끼기 위해서 IO를 처리하는 스레드를 따로 분리할 겁니다.

물론 Overlapped IO모델이라면 비동기 IO호출을 통해서 요청만하고 리턴하는 방식으로 할 수 있겠습니다만.

Overlapped io는 아직 제대로 말씀도 안드렸고, IOCP라는 단어만 언급했을 뿐, 아직 비동기함수에 대한 언급은 하지 않았기 때문에 고려하지 않겠습니다.

 

음... 이번글은 참 하나의 큰 맥락이 없었던 것 같습니다.

아마도 제가 정확히 이해를 하지 못했다는 증거가 될 수 있겠군요... 복습을 잘해야될 것같습니다.

오늘도 긴글 읽어주신 분들 감사합니다.

다음 글에서는 아마 OverlappedIO에 대한 내용정리가 있을것같습니다.

그럼 안녕히계세요.

320x100

'게임서버 > 멀티쓰레드 이론' 카테고리의 다른 글

Thread Design2  (0) 2022.02.04
Thread Design  (0) 2022.01.27
Out Of Ordering Excution  (0) 2022.01.24
Synchronization2  (0) 2022.01.21
Synchronization  (0) 2022.01.21