Chatting Server(Multi Thread)

2022. 5. 4. 23:44게임서버/namespace univ_dev

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

 

이번글의 목적은 저번글에 이어서 채팅서버에 대한 다 하지 못한 얘기를 해볼까합니다.

이전 글에서 다뤘던 내용이지만 채팅 서버의 구조는 다음과 같았습니다.

 

메시지를 처리하는 1개의 스레드와 그 외에 IO를 처리하는 워커 스레드들...

그리고 그 워커 스레드들에게서 하나의 큐를 통해 Job을 전달받는 메시지 처리 스레드가 존재했었습니다.

이 구조에서는 메시지 처리 스레드는 메시지가 없으면 큐가 계속 비었는지 있는지 확인하는 방식의 폴링을 채택했었습니다.

물론 이벤트를 이용해서 폴링을 하지 않고 잠들게 할 수 있었지만, 그렇게 하지 않고 그냥 폴링을 통해 하나의 cpu를 계속 사용하도록 만들었었습니다.

 

이부분에서는 장점도 있고 당연히 단점도 있을겁니다.

 

우선 장점으로는 구현이 매우 쉬워질겁니다.

그냥 무작정 폴링만 하면 되는 구조다보니 따로 이벤트를 사용할 필요도 없고 이벤트의 특성상 signal과 non signal상태만 존재하다 보니 연속으로 3건이 들어왔을때, 이 상황에대한 추가적인 해결방안이 들어가는등의 로직이 요구되지만, 그부분을 없앨 수 있었기 때문에 코드가 심플해진다는 장점이 있습니다.

 

단점으로는 하나의 스레드를 무한정 폴링하는 구조이기 때문에 작업이 없는 상황임에도 불구하고 퀀텀타임을 모조리 사용해버릴거고 하나의 cpu를 물고있는 상황이기 때문에 워커 스레드와의 경합을 피하기위해서는 코어를 모조리 사용하지 않게 가용 코어 - 1개의 상태를 유지해야 하는 단점도 존재했습니다.

 

하지만 더욱 근본적인 불만점은 이게 아닙니다.

과연 채팅서버에 업데이트 스레드라는 추가적인 요소 자체가 들어갈 필요가 있는지 우선 고민을 해봐야 할 것 같습니다.

당연히 채팅서버는 스스로 계산해서 로직을 만들어내는 경우는 거의 없을겁니다.

거의 99%이상이 IO를 통한 로직 처리일겁니다.(사실 100%라고 봐도 거의 무방할 정도입니다)

IO를 통해서만 Send할 것이 생기는 스레드 특성상 굳이 IO를 하고 그걸 큐잉을하고 그걸 뽑아내서 다른 스레드가 작업을 하기위한 폴링을 한다는 것 자체가 이미 낭비가 일어나고 있는것일수 있습니다.

이 상황에서 워커 스레드가 그대로 작업을 처리하는 구조가 된다면 불필요한 폴링없이 필요한 순간에만 작동하게 되므로 더욱 유연하게 작동하지 않을까 라는 점을 고민해봤습니다.

 

당연히 워커스레드가 멀티스레드로 돌기때문에 컨텐츠에서의 Lock은 불가피하게 되었지만... 모든 것을 락걸겠다는 것은 아니고 당연히 필요한 순간에 섹터 단위로의 락을 걸게 될겁니다.

 

추가로 SRWLOCK의 장점인 Shared, Exclusive를 이용한 형태로 갈것이기 때문에 검색만을 요구할때는 동시에 모든 스레드가 진입가능할것입니다.

만약 이동요청이나 로그인, 로그아웃 등과 같은 섹터나 플레이어 컨테이너의 요소를 수정하는 작업을 처리한다면 그때만 섹터에 Exclusive로 락을 걸어서 단일 스레드의 진입을 확보하면 충분히 해결 가능한 상황일것입니다.

 

그리고 다음과 같은 구조는 섹터의 갯수가 늘어나면 늘어날수록 경합의 확률이 획기적으로 줄어드는 장점을 지니고 있습니다.

제 채팅 서버의 경우에는 단일 월드로 섹터는 2500개 정도가 되기 때문에 실제 워커 스레드들끼리 경합이 일어날 확률은 현저히 낮습니다.

 

하지만 처리하지 않은 문제가 여전히 하나 남아있습니다.

Critical Section의 최대 장점이라고 할수있는 Recursion Lock이 SRWLOCK에서는 지원되지 않는다는 점입니다.

사실 이게 무슨 문제냐라고 하실수 있습니다만 Lock을 건 스레드가 또 Lock을 걸게되는 경우가 지금 네트워크 라이브러리에서 발생하고 있었습니다.

DeadLock발생 케이스

SendPacket(Sector Lock) -> SendPost(실패) -> ReleaseSession -> OnClientLeave -> RemovePlayer(Sector Lock)

다음과 같은 구조로 인하여 하나의 스레드에서 동일한 섹터에 대한 Lock을 두번걸고있고 이 는 SRWLOCK을 사용하고 있는 상황에서는 해결이 불가능한 상황입니다.

 

Critical Section을 이용한다면 충분히 해결 가능한 상황이지만 Critical Section의 경우에는 Shared, Exclusive가 없기때문에 워커 스레드간 경합 발생율이 상당히 많이 높아질 것입니다.

 

그럼 이상황에서 우리가 사용할 수 있는 방법은 한가지입니다.

다른 스레드가 OnClientLeave를 하도록 유도하게 한다 입니다.

뭐... 방법은 많습니다만 해답을 드리진 않겠습니다.

이정도 드렸으면 해답을 찾으실 수 있을것이라고 판단합니다.

 

아마 이 문제를 해결 하셨다면 멀티 스레드로 채팅 서버를 구현하실 수 있을겁니다.

당연히 멀티스레드 구조이기 때문에 원래의 구조와는 좀 달라지게 되었습니다만... 틀에서 크게 벗어나는 구조는 없을겁니다.

 

 

음... 이제 넷서버도 거의 막바지에 도달했습니다.

다음 글에서는 아마 아얘 새로운 내용을 가지고 돌아오지 않을까 싶은데요...

네트워크 라이브러리의 안정성을 검증했다면 이제 앞으로는 컨텐츠를 위주로 끼우는 형태로 나오지 않을까 싶습니다.

물론 그 이전에 DB같은것도 해야될것 같습니다.

 

오늘도 긴글 읽어주셔서 정말 감사합니다.

다음번 글에서는 더 좋은 내용으로 돌아오겠습니다.

그럼 안녕히 계세요.

320x100

'게임서버 > namespace univ_dev' 카테고리의 다른 글

Login Server  (1) 2022.05.19
NetServer(Chatting Server)  (0) 2022.05.03
LockFreeStack Trouble Shooting  (2) 2022.03.28
DumpClass  (0) 2022.03.28
LockFree  (0) 2022.03.22