웬디의 기묘한 이야기

글 작성자: WENDYS
반응형


DCLP

Double-Checked Locking Pattern

 

GOF의 23가지 패턴 중 싱글톤 패턴 (Singleton Pattern) 에서 사용하는 패턴 입니다.

단일로 사용하는 패턴은 아니고 싱글톤 패턴의 단점을 보완해주는 역할을 하게 되죠


2015/12/17 - [Development/Design Pattern] - [C++] 싱글톤 패턴 - Singleton Pattern


이전 글 에서 보면 안전해보이고 문제가 없을 것 같지만 멀티쓰레드 환경에서는 Thread safe 이슈가 있습니다.

바로 싱글톤 패턴의 핵심인 단일 인스턴스의 생성이 보장되지 않는다는것이죠

 

static singleton* instance() {
    if (_instance == nullptr) {
        _instance = new singleton();
    }
    return _instance;
}

 

자 다음과 같은 코드가 있습니다.

 

당연히 사용자는 singleton::instance()->function() 이런식으로 접근을 할 것 입니다.

 

이때 1번 Thread가 요청합니다.

아직 생성되지 않았기때문에 _instance가 nullptr 입니다. 그래서 _instance = new singleton() 을 호출하려 합니다.

 

이 때!

OS가 인터럽트를 걸고 2번 Thread에게 제어권을 넘깁니다.

아직 _instance는 nullptr 입니다

2번 Thread에서 _instance = new singleton(); 을 호출하여 메모리가 생성되었네요

 

그런데!

OS가 1번 Thread에게 제어권을 넘기게 되니 또 하나의 _instance가 생성되버리고 말았습니다.

이렇게 되면 Singleton Pattern의 핵심인 단일 instance여야 한다는 규칙이 깨지게 되었고 메모리 Leak이 발생하게 된 것 입니다.





 

그런 이유로 해당 문제를 해결하기 위해 DCLP 가 나온 것 입니다.


example


class singleton {
private:
    static singleton* _instance;
    static std::mutex _mutex;

    singleton() {}
    singleton(const singleton& other);
    ~singleton() {}
public:
    static singleton* instance() {
        if (_instance == nullptr) {

            std::lock_guard<std::mutex> lock(_mutex);

            if (_instance == nullptr) {
                _instance = new singleton();
            }
        }
        return _instance;
    }
};


위처럼 2번 체크하면서 락을 건다 해서 Double-Checked Locking Pattern 이라고 하는 것 같습니다.

여기서 Locking은 RAII 패턴으로 적용하였습니다.

2015/12/16 - [Development/Design Pattern] - [C++] RAII 패턴 - Resource Acquisition Is Initialization Pattern


하지만 여전히 문제를 앉고있습니다. 상황에 따라 컴파일러에서 최적화가 되어버리면 의도치 않게 동작할 수도 있기 때문입니다.

* 컴파일러 최적화에 의해 생성자가 호출되지 않았음에도 불구하고 _instance가 할당되어 사용되는 상황이 발생하여 버릴 수 있습니다.

 

이 때 적용할 수 있는게 volatile 입니다.

C/C++ 프로그래밍 언어에서 이 키워드는 최적화 등 컴파일러의 재량을 제한하는 역할을 한다고 합니다.

WIKI - https://ko.wikipedia.org/wiki/Volatile_%EB%B3%80%EC%88%98

 

Volatile이 적용된 example

 

class singleton {
private:
    static singleton* volatile _instance;
    static std::mutex _mutex;

    singleton() {}
    singleton(const singleton& other);
    ~singleton() {}
public:
    static singleton* volatile instance() {
        if (_instance == nullptr) {

            std::lock_guard<std::mutex> lock(_mutex);

            if (_instance == nullptr) {
                _instance = new singleton();
            }
        }
        return _instance;
    }
};

 

그래도 조금은 다듬어진 코드가 완성되었네요

 

위 코드는 문제점을 조금이나마 줄이기 위하여 많은 사람들이 생각하고 또 생각한 방법 중 하나입니다.

여러가지 기법이 있겠지만 가장 간단한 방법이라 소개드리고싶네요

 

글을 작성하다보니 위의 _instance를 그냥 포인터가 아닌 스마트포인터인 std::shared_ptr을 사용하면 어떨까 생각이 드네요~

 

또 싱글톤 클래스의 생성이 그렇게 부담스럽지 않다면 Main Initialize() 하는곳에서 각각 초기화를 해놓는건 어떨까요??


여러가지 방법이 있고 여러가지 생각을 할 수 있어 공부하기 좋은 패턴인 것 같습니다!!


감사합니다.




반응형