본문 바로가기
MultiThread

RAII 패턴과 LockGuard 방식의 스레드 제어

by W00gie 2021. 10. 27.

RAII (Resoruce Acquisition is Initialization) 패턴은 자원의 할당과 해제를 자동화해주는 패턴입니다. 

DB인증, 공유자원 등 여러 방면에서 활용될 수 있으며 코드의 안전성을 보장해줍니다.

스택에 할당된 메모리가 자동으로 해제되며 소멸자가 호출된다는 점을 이용한 패턴이며,

저는 최근 C++의 멀티스레드 프로그래밍을 공부하며 접하게 되었습니다.

 

Thread의 동기화를 제어하는 방식은 Atomic과 Lock등 여러 방법이 있습니다.

다만 Lock의 경우 lock, unlock이 set를 이루어 작동되어야합니다.

lock 이후 unlock이 이루어지지 않는 경우 무한한 대기상태에 빠지게 됩니다.

 

멀티스레드 환경에서는 이러한 문제를 LockGuard를 이용해 unlock을 보장합니다.

 

#include <mutex>

vector<int32> v;
mutex m; // mutual exclusive 상호배타적


// RAII 패턴 (Resource Acquisition is initialization)
// 생성자에서 잠그고, 소멸자에서 풀어주는 패턴
// DB연결이나 리소스접근에서 사용

template<typename T>
class LockGuard  //wrapper class role 
{
public:
	LockGuard(T& m)
	{
		_mutex = &m;
		_mutex->lock();
	}

	~LockGuard()
	{
		_mutex->unlock();
	}

private:
	T* _mutex;
};


void Push()
{
	for (int32 i = 0; i < 10000; i++)
	{
		LockGuard<std::mutex> lockGuard(m);  //lockGuard 객체 생성
		v.push_back(i);
	}
}

int main()
{
	std::thread t1(Push);
	std::thread t2(Push);

	t1.join();
	t2.join();
	
	cout << v.size() << endl;

}

실행결과 동기화 해결

임의로 생성된 LockGuard 클래스입니다.Push함수내에서 생성되어 mutex변수를 포함하는 wrapper class 로서 작동합니다.함수내에서 생성된 이상 스택영역에 저장되어 Push함수가 실행종료됨고 동시에 파괴자를 호출하며 해제됩니다.

 

일일히 lock과 unlock을 선언하는 것보다 객체 생성에 부하가 주어지지만 안전한 코드가 작성하다는 장점이 있습니다.해당 LockGuard는 C++ 스탠다드에서 이미 제공중인 기능이며 스탠다드의 함수를 이용할때는 다음과 같습니다.

 

void Push()
{
	for (int32 i = 0; i < 10000; i++)
	{
		std::lock_guard<std::mutex> lockGuard(m); //C++ 표준
		v.push_back(i);
	}
}

 

 

+) lockGuard 이외에도 lock의 실행시점을 미룰 수 있는 unique_lock등의 옵션을 가진 여러 lock 이 존재합니다.

https://en.cppreference.com/w/cpp/thread/unique_lock

 

std::unique_lock - cppreference.com

template< class Mutex > class unique_lock; (since C++11) The class unique_lock is a general-purpose mutex ownership wrapper allowing deferred locking, time-constrained attempts at locking, recursive locking, transfer of lock ownership, and use with conditi

en.cppreference.com