atomic 보다 일반적인 상황에서 사용되는 데이터 동기화 방법이다.
멀티 쓰레드 환경에서 공유하고 있는 영역을 쓰레드 하나가 들어오면 접근 할 수 없게 하는 방법입니다.
기본적으로 일반적인 STL 자료구조는 멀티쓰레드 환경에서 사용 할 수 없다고 생각을 하고 코딩을 해야 합니다.
멀티쓰레드 환경에서 vector를 push_back을 해주게 되면 crash가 발생하여 컴파일을 할 수 없습니다. 이러한 이유는 vector가 동적메모리로, vector에 새로운 데이터를 추가하기 위해서 데이터를 추가 하는 과정에서 할당된 메모리 영역이 작게 되면, 새로운 데이터 영역을 할당 받고, 기존에 있던 데이터를 옮기고, 기존 데이터는 free 할 것입니다. 이러한 과정에서 두개 이상의 쓰레드가 데이터를 추가 하고 있다고 할 때, 참조 하고 있던 메모리영역이 free가 되어 그곳에 데이터를 추가 할 수 없는 상황이 발생하게 됩니다.
이러한 상황을 해결해주기 위해 vector.reserve(20000) 을 통해 20000인덱스 크기의 영역을 할당 해준 후 값을 집어 넣어 주게되면 crash는 발생하지 않지만, 값을 손실하게 됩니다.
이러한 부분을 해결하기 위해서는 동기화가 필요하고, LOCK을 통해서 동기화를 해주어야 합니다.
#include <mutex>
mutex m;
m.lock();
m.unlock();
ex)
vector<int32> v;
mutext m;
void Push(){
for(int i=0;i<100000;i++){
m.lock();
v.push_back(i);
m.unlock();
}
}
ex)
vector<int32> v;
mutex m;
void Push(){
for(int i=0;i<100000;i++){
m.lock();
m.lock();
v.push_back(i);
m.unlock();
m.unlock();
}
}
위와 같이 하게되면 crash가 발생하게 된다. 하지만, 이 방법이 아닌 다른 재귀lock 메소드를 사용하게 되면 이 또한 가능 해지게 된다.
클래스를 이용하여 레퍼런스 객체에서 lock을 처리하고, 소멸자에서 unlock을 처리하는 구조이다.
예시
template<typename T>
class LockGuard
{
public :
LockGuard(T& m)
{
_mutex=&m;
_mutex->lock();
}
~LockGuard()
{
_mutex->unlock();
}
private :
T* _mutex;
};
vector<int32> v;
mutex m;
void Push(){
for(int i=0;i<100000;i++){
LockGuard<std::mutex> LockGuard(m);
v.push_back(i);
}
}
LockGuard 인스턴스가 생성 되면서 lock을 하게 되고 , 더이상 해당 인스턴스가 쓸모가 없게되면 소멸자를 불러와 unlock을 하게 된다.
std::lock_guard<std:mutex> lockGuard(m);
std::unique_lock<std::mutex> uniqueLock(m,std::defer_lock);
lock_guard는 위의 클래스와 같은 일을 하고, unique_lock 또한 같은 일을 하지만 조금 더 복잡한 일을 한다. 위의 예시에서는 std::defer_lock을 인수로 주어서 처음 unique_lock이 생성 됐을 때 바로 lock을 하지 않는다. 그리고 uniqueLock.lock()을 하게되면 그제서야 lock을 한다. unlock을 위와같이 소멸자가 발생할 때 일어난다.