#include <thread>
#include <mutex>
int main()
{
mutex m;
m.lock()
}
위와 같이 lock을 한 후 unlock을 하지 않게 되면 DeadLock에 걸려 무한 대기상태가 될 수 있다.
해결방법 중 하나는 lock_guard를 활용하는 것이다.
int main()
{
mutex m;
lock_guard<mutex> guard(m);
}
위와 같이 사용하게 되면 guard객체가 생성되면서 lock이 걸리고 소멸되면서 자동으로 unlock이 된다.
반복문이나 함수에서 사용하게 되면 lock과 unlock을 알아서 해주는 것이다.
물론 lock_guard가 모든 DeadLock을 해결해주는 것은 아니다.
아래 한 가지 예시가 있다.
AccountManager는 로그인을 위해 User의 정보를 필요로 하고 UserManager는 User의 정보를 저장하기 위해서 Account의 정보가 필요하다고 가정해보자.
AccountManager.h
#pragma once
#include <mutex>
class Account
{
// Account 정보
};
class AccountManager
{
public:
static AccountManager& instance()
{
static AccountManager instance;
return instance;
}
Account* GetAccount(int id)
{
lock_guard<mutex> guard(_mutex);
return nullptr;
}
void Login();
private:
mutex _mutex;
};
AccountManager.cpp
#include "AccountManager.h"
#include "UserManager.h"
void AccountManager::Login()
{
// Account Lock
lock_guard<mutex> guard(_mutex);
// User Lock
User* user = UserManager::instance().GetUser(123);
}
UserManager.h
#pragma once
#include <mutex>
class User
{
// User 정보
};
class UserManager
{
public:
static UserManager& instance()
{
static UserManager instance;
return instance;
}
User* GetUser(int id)
{
lock_guard<mutex> guard(_mutex);
return nullptr;
}
void Save();
private:
mutex _mutex;
};
UserManager.cpp
#include "UserManager.h"
#include "AccountManager.h"
void UserManager::Save()
{
// User Lock
lock_guard<mutex> guard(_mutex);
// Account Lock
Account* account = AccountManager::instance().GetAccount(123);
}
Main.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include "AccountManager.h"
#include "UserManager.h"
void Func1()
{
for (int i = 0; i < 10; i++)
{
UserManager::instance().Save();
}
}
void Func2()
{
for (int i = 0; i < 10; i++)
{
AccountManager::instance().Login();
}
}
int main()
{
thread t1(Func1);
thread t2(Func2);
t1.join();
t2.join();
cout << "Done" << endl;
}
main에서 멀티 스레드로 실행했다.
위의 코드는 정상적으로 실행될 때도 있고 무한대기 상태에 걸릴 수도 있다.
DeadLock은 항상 에러가 발생하는 것은 아니라는 특징이 있다.
테스트 단계에서는 문제가 없다가 배포를 하고 많은 이용자들이 몰리게 되면 DeadLock이 발생하는 경우가 많다.
그럼 위의 코드에서는 어떤 상황에서 DeadLock이 걸릴까?
AccountManager는 Account Lock을 획득한 후 User Lock을 얻으려고 하고
UserManager는 반대로 User Lock을 먼저 획득한 후 Account Lock을 얻으려고 한다.
이 때 각각 첫 번째 Lock을 획득하고 난 후 2번 째 Lock을 획득하려고 할 때 이미 다른 스레드가 선점하고 있어 무한정으로 기다리게 되는데 이것이 DeadLock이다.
Lock을 획득하는 순서가 다르기 때문에 발생하는 문제이다.
위와 같은 경우는 Lock을 획득하는 순서만 맞춰주게 되면 해결이 된다.
하지만 현실적으로 작업을 할 때 모든 Lock의 순서를 맞추는 것은 불가능에 가깝다.
또한 Lock 사이에 사이클이 존재하는지 검사하여 DeadLock 상태를 탐지할 수 있다.
위의 경우 Account -> User -> Account 순서로 Lock을 획득하려고 하여 사이클이 존재한다.
DeadLock을 예방하는 방법은 그냥 조심해서 사용하는 것...
그냥 쓰다가 문제가 생기면 고치는 것...
이정도가 되겠다.