lock_guard의 한계
lock_guard를 사용한다 해서
모든 데드락 현상을 방지할 수는 없다
데드락
서로 락을 하나씩 걸고
상호 기다리는 상태와 같은 현상을 데드락이라고 한다
데드락은 항상 발생하는 것이 아니라 랜덤확률로 발생하다 보니
기존 디버깅으로 발생하지 않던 버그가 갑자기 발생하는 경우가 있어
매우 곤란하다
데드락 예시 1
// AccountManager.h
class Account
{
// TODO
};
class AccountManager
{
public:
static AccountManager* Instance()
{
static AccountManager instance;
return &instance;
}
Account* GetAccount(int32 id)
{
lock_guard<mutex> guard(_mutex);
// 찾아서 뭔가를 반환
return nullptr;
}
void ProcessLogin()
{
// AccountLock
lock_guard<mutex> guard(_mutex);
// UserLock
User* account = UserManager::Instance()->GetUser(100);
}
private:
mutex _mutex;
//map<int32, Account*> _accounts;
};
// UserManager.h
class User
{
// TODO
};
class UserManager
{
public:
static UserManager* Instance()
{
static UserManager instance;
return &instance;
}
User* GetUser(int32 id)
{
lock_guard<mutex> guard(_mutex);
// 찾아서 뭔가를 반환
return nullptr;
}
void ProcessSave()
{
// UserLock
lock_guard<mutex> guard(_mutex);
// AccountLock
Account* account = AccountManager::Instance()->GetAccount(100);
}
private:
mutex _mutex;
//map<int32, Account*> _accounts;
};
void Func1()
{
for (int32 i = 0; i < 1000; i++)
{
UserManager::Instance()->ProcessSave();
}
}
void Func2()
{
for (int32 i = 0; i < 1000; i++)
{
AccountManager::Instance()->ProcessLogin();
}
}
int main()
{
std::thread t1(Func1);
std::thread t2(Func2);
t1.join();
t2.join();
cout << "Jobs Done" << endl;
}
위의 예시 코드를 보면
ProcessSave 함수에서는 UserLock을 생성하고 AccountLock을 생성한다
ProcessLogin 함수에서는 AccountLock을 생성하고 UserLock을 생성한다
이러한 상황은
서로 락을 하나씩 생성하고 다음 락을 상호 기다리는 상태가 발생하며
이를 데드락이라고 한다
데드락 해결 1
위 그림처럼 하나의 프로세스가 하나의 리소스를 락을 해놓고
다음 리소스를 기다리는 상태로 데드락이 발생하는 경우이다
이 경우는 사실
각자 중복되지 않은 것에 각각 접근해서 발생한 문제이다
따라서,
아래의 코드처럼
통일된 순서(규칙)를 지켜주면 해결할 수 있다
...
void ProcessLogin()
{
// UserLock 먼저
User* account = UserManager::Instance()->GetUser(100);
// AccountLock 다음
lock_guard<mutex> guard(_mutex);
}
...
...
void ProcessSave()
{
// UserLock 먼저
lock_guard<mutex> guard(_mutex);
// AccountLock 다음
Account* account = AccountManager::Instance()->GetAccount(100);
}
...
데드락 해결 2
위처럼 순서를 매번 신경 쓰며 지켜주는 것은 매우 불편하다
그래서 래핑 클래스로 id 우선순위 부여하는 방법 또한 있다
그런데 이도 사실 모든 우선순위를 고려해 부여하는 것도 문제이다
데드락 해결 3
또 다른 해결책으로는
자원공유 상태를 그래프로 만들어서
cycle이 발생하는지 판단하도록 하는 방법이다
데드락 해결 결론
사실 데드락을 해결하기 위한 다양한 방법들이 있지만
100퍼 방지하는 법은 없다ㅠㅠ
mutex 참고 기능
- std::lock
mutex m1;
mutex m2;
std::lock(m1, m2);
해당 기능을 이용해 여러 개의 mutex를 걸어놓으면
작성한 순서가 아닌 내부적으로 일관된 순서가 보장되도록 작동되도록 한다
- std::adopt_lock
std::unique_lock<std::mutex> unique_lock(m1); // 뮤텍스를 잠금
std::lock_guard<std::mutex> guard(m1, std::adopt_lock); // unique_lock으로부터 잠금을 인계받음
// 잠금 해제는 guard의 소멸자에서 자동으로 처리됨
std::lock이나 std::unique_lock을 통해서
lock을 이미 수동으로 작성해서 실행했으니
소멸되는 것만 관리해 달라고 옵션을 작성하는 기능이다
'게임 서버' 카테고리의 다른 글
[게임서버 섹션2 Note] SpinLock (0) | 2024.12.29 |
---|---|
[게임서버 섹션2 Note] Lock 구현 이론 (0) | 2024.12.29 |
[게임서버 섹션2 Note] Lock 기초 (0) | 2024.12.28 |
[게임서버 섹션2 Note] Atomic (0) | 2024.12.28 |
[게임서버 섹션2 Note] 스레드 생성 (0) | 2024.12.28 |