LOCK

원래벌레·2022년 4월 7일
0

atomic 보다 일반적인 상황에서 사용되는 데이터 동기화 방법이다.
멀티 쓰레드 환경에서 공유하고 있는 영역을 쓰레드 하나가 들어오면 접근 할 수 없게 하는 방법입니다.

기본적으로 일반적인 STL 자료구조는 멀티쓰레드 환경에서 사용 할 수 없다고 생각을 하고 코딩을 해야 합니다.

💎Vector의 움직임

  • 멀티쓰레드 환경에서 vector를 push_back을 해주게 되면 crash가 발생하여 컴파일을 할 수 없습니다. 이러한 이유는 vector가 동적메모리로, vector에 새로운 데이터를 추가하기 위해서 데이터를 추가 하는 과정에서 할당된 메모리 영역이 작게 되면, 새로운 데이터 영역을 할당 받고, 기존에 있던 데이터를 옮기고, 기존 데이터는 free 할 것입니다. 이러한 과정에서 두개 이상의 쓰레드가 데이터를 추가 하고 있다고 할 때, 참조 하고 있던 메모리영역이 free가 되어 그곳에 데이터를 추가 할 수 없는 상황이 발생하게 됩니다.

  • 이러한 상황을 해결해주기 위해 vector.reserve(20000) 을 통해 20000인덱스 크기의 영역을 할당 해준 후 값을 집어 넣어 주게되면 crash는 발생하지 않지만, 값을 손실하게 됩니다.

  • 이러한 부분을 해결하기 위해서는 동기화가 필요하고, LOCK을 통해서 동기화를 해주어야 합니다.

💎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();
    }
}
  • m.lock() 을 통해서 m.unlock()이 되기 전까지의 공유 영역을 하나의 쓰레드만 참조 할 수 있게 됩니다. 만약에 다른 쓰레드가 해당 공유 영역을 참조 하려고 하면 lock에 막혀 대기하게 됩니다.

💎 LOCK의 재귀 사용

  • lock을 재귀 호출 할 수 있는가 ?
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 메소드를 사용하게 되면 이 또한 가능 해지게 된다.

  • 그러면 왜 재귀락이 필요한가 ?
    1) 크기가 커진 프로그램은 함수 내에서 재귀 lock을 사용하여 코드를 다양하게 짤 수 있다.
    2) 실수로 lock을 해주고 unlock을 안해준 경우 ex) if(i==5000) break; 이런 경우 unlock을 빼먹을 수도 있다.

💎 RAII 패턴

  • 클래스를 이용하여 레퍼런스 객체에서 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을 하게 된다.

  • 이러한 LockGuard는 언어에서 기본적으로 제공을 하고 있다.
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을 위와같이 소멸자가 발생할 때 일어난다.

profile
학습한 내용을 담은 블로그 입니다.

0개의 댓글