티스토리 뷰

개발/C++

싱글턴 패턴 동기화 처리

부캐: 개발하는 조대리 2023. 5. 6. 12:34
반응형

싱글턴 패턴 동기화 처리

싱글턴 패턴은 애플리케이션에서 특정 클래스의 인스턴스가 오직 하나만 만들어지도록 보장하는 디자인 패턴입니다. 이 패턴을 사용하면 클래스의 인스턴스를 전역 변수로 사용할 때와 같은 편리함을 누릴 수 있으면서도, 전역 변수와 같은 문제점 (모듈성 파괴 등)을 회피할 수 있습니다.


C++11 이전까지는 정적 변수를 초기화하는 순서가 보장되지 않았기 때문에 멀티스레딩 환경에서는 동기화 문제가 발생할 수 있었습니다. 그러나 C++11부터는 이 문제가 해결되었으므로, C++11 이상에서는 정적 멤버 변수를 사용하는 것만으로도 스레드 안전성을 보장할 수 있습니다.

다음은 싱글턴 패턴을 사용하여 클래스의 인스턴스를 만드는 예시입니다. 이 예시에서는 정적 멤버 변수를 사용하여 인스턴스를 만들고, 정적 메서드를 사용하여 인스턴스에 접근합니다. 이때, 인스턴스는 처음 접근할 때 생성됩니다.

 

< C++11 부터 지원 가능함 >

// C++11 부터 지원
class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;
        return instance;
    }

    void doSomething() {
        // ...
    }

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

위의 코드에서, 정적 멤버 변수 instance는 instance() 메서드가 처음 호출될 때 생성됩니다. 그리고 이후로는 항상 동일한 인스턴스를 반환합니다. 이렇게 하면 언제 어디서든 동일한 인스턴스에 접근할 수 있습니다. 생성자와 소멸자는 private으로 선언하여 외부에서 인스턴스를 생성하거나 삭제하는 것을 막습니다. 또한 복사 생성자와 대입 연산자도 private으로 선언하여 객체 복사를 막습니다.

C++11 이전 버전을 사용하고 있다면, 멀티스레딩 환경에서도 싱글턴 패턴을 안전하게 사용하기 위해서는 추가적인 동기화 작업이 필요합니다. 가장 간단한 방법은 std::mutex를 사용하는 것입니다. 이 방법은 instance() 메서드가 호출될 때마다 뮤텍스를 이용하여 인스턴스 생성을 동기화합니다.

 

< C++11 이전 일 경우>

// C++11 이전 구현 방법
class Singleton {
public:
    static Singleton& instance() {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!instance_) {
            instance_ = new Singleton();
        }
        return *instance_;
    }

    void doSomething() {
        // ...
    }

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance_;
    static std::mutex mutex_;
};

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

위의 코드에서, instance_와 mutex_는 정적 멤버 변수로 선언되었습니다. instance() 메서드는 뮤텍스를 이용하여 인스턴스 생성을 동기화하고, 생성된 인스턴스의 포인터를 반환합니다.

 

싱글턴 패턴에서 특정 함수가 멀티스레딩 환경에서 호출될 경우

싱글턴 패턴에서 특정 함수가 멀티스레딩 환경에서 호출될 가능성이 있고, 그 함수 내부에서 클래스의 멤버 변수를 변경하는 경우, 즉 동시에 여러 스레드가 해당 함수를 호출하면 멤버 변수의 값이 예기치 않게 변경될 수 있습니다.
따라서, 멤버 변수를 변경하는 함수에 대해서는 적절한 동기화 작업을 수행해야 합니다.

C++11 이상에서는 정적 멤버 변수를 사용하는 경우에는 추가적인 동기화 작업이 필요하지 않지만, C++11 이전 버전에서는 std::mutex와 같은 동기화 기법을 사용하여 수동으로 동기화 작업을 수행해야 합니다.

아래는 참조 소스입니다.

 

< C++11부터 지원 가능함 >

// C++11 부터 지원
class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }

    void setValue(int value) {
        value_ = value;
    }

    int getValue() const {
        return value_;
    }

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static int value_;
};

int Singleton::value_ = 0;

정적 멤버 변수는 클래스의 모든 객체에서 공유되는 변수입니다. 따라서, 멤버 함수에서 정적 멤버 변수를 참조하는 경우에는 동기화 작업이 필요하지 않습니다.

 

< C++11 이전 일 경우>

// C++11 이전 구현 방법
class Singleton {
public:
    void setValue(int value) {
        std::lock_guard<std::mutex> lock(mutex_);
        value_ = value;
    }

    int getValue() const {
        return value_;
    }

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    int value_;
    std::mutex mutex_;
};

위의 코드에서, setValue() 함수는 std::lock_guard를 사용하여 mutex_를 락하고, value_ 멤버 변수를 변경합니다. 이렇게 하면 멀티스레딩 환경에서도 value_ 멤버 변수의 값이 안전하게 변경됩니다.

따라서, 동시에 여러 스레드에서 호출될 가능성이 있는 함수에서는 적절한 동기화 작업을 수행하여 멤버 변수의 값이 예기치 않게 변경되지 않도록 해야 합니다.

 

개인적으로 학습하면서 정리한 내용입니다.

잘못된 내용이 있을 경우 알려주시면 확인 후 수정 및 반영하도록 하겠습니다.

 

오늘도 감사합니다.(__)>