이 블로그 검색

2024년 2월 13일 화요일

CPP> std::lock_guard 뮤텍스 활용법.

CPP은 직접 메모리를 할당하고 제어하다보니, 포인터를 사용하여 힙에 메모리를 할당한 후,
해당 주소를 반환해서 쓰는 경우가 많다.


그렇다보니, 잠깐 뭔가를 하는 경우에도, new로 클래스를 할당하고, 끝날 때 delete를 호출해야하는 경우가 발생했다. 

그런데, 이 delete를 호출하기전에, 중간에 return이 되는 경우와 같이 delete가 호출되지 않거나, 코드 작성자가 까먹거나 하는 일이 생기는데, 매번 new가 호출될 때마다, 메모리가 할당되므로

여러번 호출되는 함수에서 이런 실수가 발생하면 해당 프로세스가 메모리를 점점 잡아먹다가, 시스템을 먹통이 되게 만들거나, OS에서 해당 프로세스를 정지하거나 하는 등, 정상적인 작동을 하지 못하게 된다. 이것을 메모리 누수라고 부르는데, 

이 메모리 누수를 막기 위해, 보통의 지역변수들처럼, 스택에서 무언갈 하다가, 스택이 pop되면 delete를 자동 호출하기 위해, 스마트 포인터라는 것이 도입 되었다.  실제로는 굉장히 복잡하겠지만 간단히 설명하자면 다음과 같다. 
template<class T> 
class smart_p {
    public:
        T* ptr; 
        operator= (smart_p<T> a, T* b){
            ptr = b;
        }
        smart_p(T* b) {
            ptr = b;
        }
        void ~smart_p() {
            delete ptr;
        }
}
new에서 반환하는 메모리 주소를 받아다가 내부 메모리 주소를 저장하는 클래스를 만들고 클래스를 스택에 만들어 사용하는 것이다. 
그러면 스택에 끝에서 자동으로 메모리의 해제가 발생한다. 

시작이 있으면 반드시 끝이 있어야 하는 것 가운데에는 mutex라는 것도 있다.
mutex는 lock을 걸어주면 반드시 unlock을 걸어주어야, 다른 프로세스가 일하기를 대기하다가 일을 시작할 수가 있다, 

메모리 누수는 서서히 시스템이 죽어가지만 mutex를 unlock하지 않으면 그냥 시스템이 죽는다. 

mutex도 스마트포인터처럼 같은 스택에 클래스를 만들어 사용할 수 있다. std::lock_guard이 그와 같은 일을 한다.  스마트 포인터는 메모리 생성은 그래도 직접 new를 호출하는 방식을 많이 사용했는데, (아무래도 팩토리 방식을 쓸 수도 있으니까)

mutex는 lock_guard도 직접 거는 방식을 사용한다. 대략 간단히 쓰자면 다음과 같겠지.

template<class T> 
class lock_guard {
    public:
        T& m; 
      
        lock_guard(T& b) {
            m = b;  m.lock(); 
        }
        void ~lock_guard() {
            m.unlock();
        }
}

그 외,std::lock이라는  여러개의 mutex를 동시에 받아서 lock을 걸 수도 있다. 

문서를 읽어보자.

각각의 lock을 직접 건다면, 사용 방법에 따라, ABC, BAC 순인 lock이 걸릴 때,

상호간 무한 대기상태에 빠질 수 있으므로 이런 방법을 사용한다.
(두 항목들간 복사 같은 상황) 

실제 구현은 std::atomic 등을 사용해, 락을 동시에 거는 방법 등을 사용하는 것이 아닐까!,  추측한다. 

댓글 1개:

  1. 스마트포인터는... 이제 저기에 레퍼런스 카운터 같은거 붙기 시작하면 덕지 덕지 복잡해 진다.

    답글삭제

가장 많이 본 글