728x90
반응형
DeadLockProfiler 설계 및 적용
- 멀티스레드 환경에서 서로 다른 스레드가 서로의 락을 기다리는 상황이 발생하면, 프로그램은 멈춘다
- 디버그 단계에서 이를 미리 탐지하기 위한 코드
h
#pragma once
#include <stack>
#include <map>
#include <vector>
/*--------------------
DeadLockProfiler -> 디버그 모드에서만 작동
---------------------*/
class DeadLockProfiler
{
public:
void PushLock(const char* name); // 어떤 락을 사용하고있는지를 받아주는 name
void PopLock(const char* name);
void CheckCycle(); //사이클 체크
private:
void Dfs(int32 here);
private:
unordered_map<const char*, int32> _nameToId;
unordered_map<int32, const char*> _idToName;
stack<int32> _lockStack;
map<int32, set<int32>> _lockHistory;
Mutex _lock;
private:
vector<int32> _discoveredOrder; // 노드가 발견된 순서를 기록하는 배열
int32 _discoveredCount = 0; // 노드가 발견된 순서
vector<bool> _finished; // Dfs(i)가 종료 되었는지 여부
vector<int32> _parent; //발견된 부모노드
};
cpp
#include "pch.h"
#include "DeadLockProfiler.h"
/*--------------------
DeadLockProfiler
---------------------*/
void DeadLockProfiler::PushLock(const char* name)
{
LockGuard guard(_lock);
// 아이디를 찾거나 발급한다.
int32 lockId = 0;
auto findIt = _nameToId.find(name);
if (findIt == _nameToId.end())
{
lockId = static_cast<int32>(_nameToId.size());
_nameToId[name] = lockId;
_idToName[lockId] = name;
}
else
{
lockId = findIt->second;
}
// 잡고 있는 락이 있었다면
if (_lockStack.empty() == false)
{
// 기존에 발견되지 않은 케이스라면 데드락 여부 다시 확인한다.
const int32 prevId = _lockStack.top();
if (lockId != prevId)
{
set<int32>& history = _lockHistory[prevId];
if (history.find(lockId) == history.end())
{
history.insert(lockId);
CheckCycle();
}
}
}
_lockStack.push(lockId);
}
void DeadLockProfiler::PopLock(const char* name)
{
LockGuard guard(_lock);
if (_lockStack.empty())
CRASH("MULTIPLE_UNLOCK");
int32 lockId = _nameToId[name];
if (_lockStack.top() != lockId)
CRASH("INVALID_UNLOCK");
_lockStack.pop();
}
void DeadLockProfiler::CheckCycle()
{
const int32 lockCount = static_cast<int32>(_nameToId.size());
_discoveredOrder = vector<int32>(lockCount, -1);
_discoveredCount = 0;
_finished = vector<bool>(lockCount, false);
_parent = vector<int32>(lockCount, -1);
for (int32 lockId = 0; lockId < lockCount; lockId++)
Dfs(lockId);
// 연산이 끝났으면 정리한다.
_discoveredOrder.clear();
_finished.clear();
_parent.clear();
}
void DeadLockProfiler::Dfs(int32 here)
{
if (_discoveredOrder[here] != -1)
return;
_discoveredOrder[here] = _discoveredCount++;
// 모든 인접한 정점을 순회한다.
auto findIt = _lockHistory.find(here);
if (findIt == _lockHistory.end())
{
_finished[here] = true;
return;
}
set<int32>& nextSet = findIt->second;
for (int32 there : nextSet)
{
// 아직 방문한 적이 없다면 방문한다.
if (_discoveredOrder[there] == -1)
{
_parent[there] = here;
Dfs(there);
continue;
}
// here가 there보다 먼저 발견되었다면, there는 here의 후손이다. (순방향 간선)
if (_discoveredOrder[here] < _discoveredOrder[there])
continue;
// 순방향이 아니고, Dfs(there)가 아직 종료하지 않았다면, there는 here의 선조이다. (역방향 간선)
if (_finished[there] == false) //이곳에 들어오면 무조건 크래쉬가 남
{
printf("%s -> %s\n", _idToName[here], _idToName[there]);
int32 now = here;
while (true)
{
printf("%s -> %s\n", _idToName[_parent[now]], _idToName[now]);
now = _parent[now];
if (now == there)
break;
}
CRASH("DEADLOCK_DETECTED");
}
}
_finished[here] = true;
}
📌 DeadLockProfiler의 흐름
1. 락 사용 추적
void PushLock(const char* name);
void PopLock(const char* name);
- 어떤 락을 잡는지(PushLock)와 해제하는지(PopLock)를 추적
- 락 이름을 문자열로 받고, 내부적으로 고유한 ID를 할당해 사용
2. 락 간 순서 추적
map<int32, set<int32>> _lockHistory;
- 락을 잡는 순서를 그래프로 저장
예: LockA → LockB 라면 A → B 간선 생성
3. 사이클 탐지
void CheckCycle();
- 락 사용 그래프에서 사이클이 생기면 데드락 가능성이 있다는 뜻
- DFS 기반의 그래프 탐색으로 사이클을 탐지
- 사이클이 발견되면 CRASH("DEADLOCK_DETECTED") 로 강제 종료
/*---------------
Crash
---------------*/
#define CRASH(cause) \
{ \
uint32* crash = nullptr; \
__analysis_assume(crash != nullptr); \
*crash = 0xDEADBEEF; \
}
사이클 탐지 알고리즘
사이클 탐지는 방향 그래프에서 순환(cycle)을 찾는 것과 동일
DFS 중 핵심 로직
if (_finished[there] == false)
{
// here -> there 로 이어지는 역방향 간선 = 사이클 존재!
CRASH("DEADLOCK_DETECTED");
}
AccountManager
#pragma once
class AccountManager
{
USE_LOCK;
public:
void AccountThenPlayer();
void Lock();
};
extern AccountManager GAccountManager;
//-----------cpp
AccountManager GAccountManager;
void AccountManager::AccountThenPlayer()
{
WRITE_LOCK;
GPlayerManager.Lock();
}
void AccountManager::Lock()
{
WRITE_LOCK;
}
PlayerManager
#pragma once
class PlayerManager
{
USE_LOCK;
public:
void PlayerThenAccount();
void Lock();
};
extern PlayerManager GPlayerManager;
PlayerManager GPlayerManager;
void PlayerManager::PlayerThenAccount()
{
WRITE_LOCK;
GAccountManager.Lock();
}
void PlayerManager::Lock()
{
WRITE_LOCK;
}
Main
#include "PlayerManager.h"
#include "AccountManager.h"
int main()
{
GThreadManager->Launch([=]
{
while (true)
{
cout << "PlayerThenAccount" << endl;
GPlayerManager.PlayerThenAccount();
this_thread::sleep_for(100ms);
}
});
GThreadManager->Launch([=]
{
while (true)
{
cout << "AccountThenPlayer" << endl;
GAccountManager.AccountThenPlayer();
this_thread::sleep_for(100ms);
}
});
GThreadManager->Join();
}
결과 화면
728x90
반응형
'서버 > 멀티쓰레드 프로그래밍' 카테고리의 다른 글
Reader-Write Lock (0) | 2025.06.04 |
---|---|
Lock Free Stack (0) | 2025.06.02 |
LockQueue LockStack (0) | 2025.06.02 |
TLS (Thread Local Storage) (0) | 2025.06.02 |
메모리 모델 (1) | 2025.05.30 |