lock_guard
lock_guard
란 c++11 부터 제공되는 클래스이다.
C++의 RAII(Resource Aquisition Is Initailization)패턴을 이용하여 생성자를 통해 lock을 Acquire하고 소멸자를 통해 lock을 Release하도록 구현하여, 동기화 객체를 사용하기 편리하게 해주는 기능이다.
lock_gaurd
를 사용하게되면 스코프를 벗어나면 자동으로 lock이 해제가 되기 때문에, unlock을 누락하는 실수를 방지할 수 있다, 따라서 직접 lock, unlock을 호출하는 것보다 lock_guard
를 사용하는 것을 권장한다.
c++ 표준에서 이러한 기능을 제공하고 있지만, 해당 lock_guard
는 c++ 표준에서 제공하는 동기화 객체를 넘겨주어야한다. std::mutex
는 CRITICAL_SECTION
을 래핑한 것이고, 이는 자원의 독점(exclusive), 공유(shared)모드를 지정하지 못한다.
해당 기능을 지원하는 SRWLOCK
을 래핑한 std::shared_mutex
라는 것이 존재하지만, 해당 mutex를 lock_guard
에 넣으면 무조건 exclusive모드로 lock을 획득하도록 설계되어 있어서 shared모드를lock_guard
로 사용하지 못한다는 단점이 있다.
따라서 lock_guard
에서 exclusive와 shared를 선택할 수 있도록 제작해볼 예정이다.
lock_guard 구현 살펴보기
먼저 c++표준에서lock_guard
를 어떻게 구현하였는지 살펴보자.
_EXPORT_STD template <class _Mutex>
class _NODISCARD_LOCK lock_guard { // class with destructor that unlocks a mutex
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
_MyMutex.lock();
}
lock_guard(_Mutex& _Mtx, adopt_lock_t) noexcept // strengthened
: _MyMutex(_Mtx) {} // construct but don't lock
~lock_guard() noexcept {
_MyMutex.unlock();
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
정말 간단하게, mutex객체를 래핑하여 생성자에서 lock()
을 호출하여 lock의 소유권을 획득하고, 소멸자에서 unlock()
을 호출하여 소유권을 해제하는 전형적인 RAII 패턴을 사용한 것을 볼 수 있다.
해당 구현을 참고하여 SRWLOCK
전용 LockGuard 클래스를 만들어 보았다.
SRWLock LockGuard 구현
enum LOCK_TYPE
{
SHARED,
EXCLUSIVE,
};
template <LOCK_TYPE Mode>
class LockGuard
{
public:
LockGuard(SRWLOCK& lock) : _lock(lock)
{
if constexpr (Mode == SHARED)
{
AcquireSRWLockShared(&lock);
}
else
{
AcquireSRWLockExclusive(&lock);
}
}
~LockGuard()
{
if constexpr (Mode == SHARED)
{
ReleaseSRWLockShared(&_lock);
}
else
{
ReleaseSRWLockExclusive(&_lock);
}
}
private:
SRWLOCK& _lock;
};
SRWLOCK
을 래핑하여 구현하였다.
위와 같이 템플릿을 이용하여, SHARED, EXCLUSIVE 상태 각각의 LockGuard를 구현하였고, if constexpr 키워드를 이용해 컴파일 타임에 해당 LockGuard가 shared모드인지 exclusive모드인지 판단할 수 있도록 구현하였다. 이렇게 하여 LockGuard 선언시점에 해당 락을 어떠한 모드로 사용할지 템플릿으로 넘겨주어 생성하게 구현함으로써 기존 lock_guard의 문제점인 무조건 exclusive로 동작하는 문제점을 해결하였다.
사용예시
//... 로직
Session* session = nullptr;
{
// 임계영역
LockGuard<SHARED> lock(gLock);
auto it = gSessionMap.find(sessionId);
if (it != gSessionMap.end())
session = it->second;
}
//... 로직
위와 같이 여러 쓰레드에서 SessionMap에 대한 접근을 할 것이고, 이를 동기화하기 위해 lock을 걸어야한다. 이때 저런식으로 임계영역에 Scope를 지정해주고 해당 Scope안에 LockGuard를 선언해주면, 동기화 객체의 사용이 편해지고 실수도 줄어들게된다.
LockGuard를 조금 더 편하게 사용하기 위한 매크로도 만들어보고 SRWLOCK을 한번더 래핑하여 Initialize해주는 부분도 생성자로 대체하도록 개선해볼 예정이다.
'SERVER > Multi-Thread' 카테고리의 다른 글
스레드의 생성과 종료(_beginthreadex, _endthreadex 소스코드 분석) (1) | 2024.02.12 |
---|---|
[Thread] 스핀락(SpinLock) (0) | 2022.09.03 |
게임개발자를 꿈꾸는 대학생의 개발 공부 블로그
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!