깃허브 주소 : CPP20_GameServer
이번에는 멀티스레딩의 대표적인 문제인 DeadLock을 탐지하는 기능을 만들어봤다.
이미 DeadLock을 예방하는 API나 자료구조는 많이 나와 있지만, 그런 API를 과하게 쓰면 오히려 프로그램이 과부하가 오거나 성능적으로 부족하다고 느낄 수 있다.
그래서 이번에는 직접 DeadLock을 탐지해서 로그를 남기는 기능을 만들고 확인해보려 한다.
C++20 기능을 활용하면서, 지난번에 만든 매크로 대체용 CRASH 인라인 함수도 사용해 실제로 적용이 가능한지 시험해본다.
각 스레드가 Lock을 잡는 과정에서 고유한 ID를 부여하고, Lock들의 관계를 기록해 그래프를 만든다.
그 그래프를 탐색할 때 순방향으로만 탐색된다면 정상적으로 Lock이 걸린 상황이지만, 역방향 탐색이 발생한다면 Lock 간에 서로가 서로를 바라보는 구조가 된다.
이 경우 DeadLock이 발생했다고 판단하고 로그를 남기도록 했다.
DeadLockProfiler - PushLock
Id와 name을 관리하기 위해 unordered_map을 사용했다. 자동 정렬을 피하면서 두 값을 함께 관리하기에 적합하기 때문이다.
이어서 Lock을 관리한 적이 없다면 새로 만들고 있다면 바로 찾아낸다.
그리고 stack 자료구조를 사용해서 Lock의 고유 id을 넣어서 어떤 Lock이 존재하는지 체크한다.
DeadLockProfiler - PopLock
Lock을 다 사용하면 해제를 해야 한다.
그래서 각각 예외 안전성을 체크하면서 마지막에 stack에 있는 요소를 삭제한다.
이 과정에서도 예외가 발생하면 CRASH 인라인 함수를 호출해 문제를 드러낸다.
DeadLockProfiler - CheckCycle
새로운 Lock이 들어오면 Lock들의 연결 상태를 확인하기 위해 CheckCycle을 호출한다.
Lock 개수를 확인한 뒤, DFS 알고리즘으로 탐색하기 위해 필요한 변수를 초기화한다.
탐색이 끝나면 다시 정리해 다른 스레드가 편하게 사용할 수 있도록 한다.
DeadLockProfiler - Dfs
DFS(깊이 우선 탐색)는 그래프 탐색에서 널리 쓰이는 알고리즘이다.
하나의 노드에서 연결된 노드 중 하나를 선택해 끝까지 탐색한 뒤, 다시 돌아와 탐색하지 않은 노드를 찾는 방식이다.
코드에서는 먼저 _discoveredOrder 배열을 확인해 이미 방문했는지 체크한다.
그리고 그래프 방향을 검사하는 부분으로 넘어간다.
만약 이전에 탐색(here)했던 Lock이 이번에 탐색(there)했던 Lock보다 늦게 발견되고 아직 그래프 탐색이 끝나지 않았다면 그것은 역방향이라는 것을 알 수 있다.
그래서 이제 어떤 Lock들이 DeadLock을 일으켰는지 로그를 찍어볼건데, 이번 C++20에 새로 생긴 format을 사용해봤다.
사실 이부분 빼고는 달라진 점은 없기에 빠르게 format 형식을 이용해서 로그를 찍고 CRASH 인라인 함수를 호출한다.
Inflearn [Rookiss][C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버