Meyers의 Singleton 패턴 스레드 구현은 안전합니까?


145

Singleton(Meyers 'Singleton) 스레드 의 지연 초기화를 사용하는 다음 구현은 안전합니까?

static Singleton& instance()
{
     static Singleton s;
     return s;
}

그렇지 않다면 왜 그리고 어떻게 스레드를 안전하게 만드는가?


누군가 이것이 스레드 안전하지 않은 이유를 설명해 주시겠습니까? 링크에서 언급 된 기사는 대체 구현을 사용하여 스레드 안전성에 대해 설명합니다 (포인터 변수 (예 : 정적 Singleton * pInstance) 사용).
Ankur

1
다음을 참조하십시오 : stackoverflow.com/questions/449436/…
Martin York


답변:


168

에서 C ++ (11) , 그것은 스레드 안전합니다. 에 따르면 표준 , §6.7 [stmt.dcl] p4:

변수가 초기화되는 동안 제어가 동시에 선언에 들어가면 동시 실행 은 초기화가 완료 될 때까지 기다려야 합니다.

이 기능에 대한 GCC 및 VS 지원 ( MSN에서 Magic Statics 라고도 하는 동시성 동적 초기화 및 파괴 )은 다음과 같습니다.

의견을 주신 @Mankarse와 @olen_gam에게 감사합니다.


에서 C ++ 03 이 코드는 스레드로부터 안전하지 않았다. "C ++ 및 이중 검사 잠금의 위험" 이라는 Meyers의 기사 에서 스레드 안전 패턴 구현에 대해 설명하고 결론은 (C ++ 03에서) 인스턴스화 방법에 대한 전체 잠금입니다. 는 기본적으로 모든 플랫폼에서 적절한 동시성을 보장하는 가장 간단한 방법이며, 대부분의 이중 검사 잠금 패턴 변형은 특정 아키텍처 에서 경쟁 조건을 겪을 수 있습니다 ( 명령이 전략적으로 메모리 장벽을 배치하지 않는 한).


3
Modernes C ++ Design에서 Alexandrescu의 Singleton Pattern (수명 및 스레드 안전성)에 대한 광범위한 토론도 있습니다. 로키의 사이트를 참조하십시오 loki-lib.sourceforge.net/index.php?n=Pattern.Singleton
마티유 M.

1
boost :: call_once를 사용하여 스레드 안전 싱글 톤을 만들 수 있습니다.
CashCow

1
불행히도이 표준 부분은 Visual Studio 2012 C ++ 컴파일러에서 구현되지 않습니다. "C ++ 11 핵심 언어 기능 : 동시성"표에서 "매직
스태틱

표준의 스 니펫은 구성을 다루지 만 파괴는 아닙니다. 표준은 다른 스레드가 프로그램 종료시 다른 스레드가 액세스하려고 시도하는 동안 한 스레드에서 객체가 파괴되는 것을 방지합니까?
stewbasic

IANA (C ++ language) L이지만 섹션 3.6.3 [basic.start.term] p2는 객체가 파괴 된 후에 객체에 액세스하여 정의되지 않은 동작을 수행 할 수 있다고 제안합니까?
stewbasic

21

스레드 안전하지 않은 이유에 대한 질문에 대답하기 위해 첫 번째 호출이에 instance()대한 생성자를 호출해야 하기 때문이 아닙니다 Singleton s. 스레드 안전을 위해서는 이것이 중요한 섹션에서 발생해야하지만, 표준에서는 중요한 섹션을 취할 필요가 없습니다 (현재까지의 표준은 스레드에서 완전히 침묵합니다). 컴파일러는 종종 간단한 검사와 정적 부울 증가를 사용하여이를 구현하지만 중요 섹션에는 없습니다. 다음 의사 코드와 같은 것 :

static Singleton& instance()
{
    static bool initialized = false;
    static char s[sizeof( Singleton)];

    if (!initialized) {
        initialized = true;

        new( &s) Singleton(); // call placement new on s to construct it
    }

    return (*(reinterpret_cast<Singleton*>( &s)));
}

여기 간단한 스레드 안전 싱글 톤 (Windows 용)이 있습니다. 컴파일러는 Windows CRITICAL_SECTION 객체에 간단한 클래스 래퍼를 사용하여 컴파일러가 CRITICAL_SECTION이전을 자동으로 초기화하도록 할 수 있습니다 main(). 중요 섹션이 개최 될 때 발생할 수있는 예외를 처리 할 수있는 진정한 RAII 임계 섹션 클래스가 이상적으로 사용되지만이 답변의 범위를 벗어납니다.

기본 작업은 인스턴스 Singleton가 요청되고 잠금이 수행되고 필요한 경우 싱글 톤이 생성 된 다음 잠금이 해제되고 싱글 톤 참조가 반환되는 것입니다.

#include <windows.h>

class CritSection : public CRITICAL_SECTION
{
public:
    CritSection() {
        InitializeCriticalSection( this);
    }

    ~CritSection() {
        DeleteCriticalSection( this);
    }

private:
    // disable copy and assignment of CritSection
    CritSection( CritSection const&);
    CritSection& operator=( CritSection const&);
};


class Singleton
{
public:
    static Singleton& instance();

private:
    // don't allow public construct/destruct
    Singleton();
    ~Singleton();
    // disable copy & assignment
    Singleton( Singleton const&);
    Singleton& operator=( Singleton const&);

    static CritSection instance_lock;
};

CritSection Singleton::instance_lock; // definition for Singleton's lock
                                      //  it's initialized before main() is called


Singleton::Singleton()
{
}


Singleton& Singleton::instance()
{
    // check to see if we need to create the Singleton
    EnterCriticalSection( &instance_lock);
    static Singleton s;
    LeaveCriticalSection( &instance_lock);

    return s;
}

사람-그것은 "더 나은 세상을 만들기"위해 많은 쓰레기입니다.

이 구현의 주요 단점은 다음과 같습니다 (버그가 해결되지 않은 경우).

  • 경우 new Singleton()가 발생, 잠금이 해제되지 않습니다. 이것은 내가 가지고있는 간단한 것 대신 진정한 RAII 잠금 객체를 사용하여 해결할 수 있습니다. 또한 Boost와 같은 것을 사용하여 잠금을위한 플랫폼 독립적 인 래퍼를 제공하는 경우 물건을 이식 할 수 있습니다.
  • 이렇게하면 Singleton 인스턴스가 main()호출 된 후 요청 될 때 스레드 안전성이 보장됩니다. 그 전에 호출하면 (정적 객체의 초기화와 같이) 초기화되지 않았기 때문에 작동 CRITICAL_SECTION하지 않을 수 있습니다.
  • 인스턴스가 요청 될 때마다 잠금을 수행해야합니다. 내가 말했듯이, 이것은 간단한 스레드 안전 구현입니다. 더 나은 것이 필요하거나 이중 검사 잠금 기술과 같은 문제에 왜 결함이 있는지 알고 싶다면 Groo의 답변에서 링크 된 논문을 참조하십시오 .

1
어 오. 하면 어떻게됩니까 new Singleton()던져?
sbi

@Bob-적절한 라이브러리 세트로 공정성을 유지하기 위해 모든 크래프트는 복사 불가능 및 적절한 RAII 잠금과 관련이 있습니다. 그러나 나는 그 예가 합리적으로 독립적이기를 원했다. 싱글 톤은 최소한의 이득을 얻기 위해 많은 작업을했지만 글로벌 사용 관리에 유용한 것으로 나타났습니다. 그들은 단지 이름 지정 규칙보다 조금 더 나은 위치와 시간을 찾는 것이 더 쉬운 경향이 있습니다.
Michael Burr

@ sbi :이 예제에서 new Singleton()던져 지면 분명히 잠금에 문제가 있습니다. lock_guardBoost 와 같은 적절한 RAII 잠금 클래스를 사용해야합니다 . 나는 예제가 다소 독립적이기를 원했고, 이미 약간의 괴물이어서 예외 안전을 피했습니다 (그러나 불렀습니다). 어쩌면이 코드가 부적절한 곳에서 잘라 붙여 넣지 않도록 수정해야 할 수도 있습니다.
Michael Burr

싱글 톤을 동적으로 할당하는 이유는 무엇입니까? 왜 'pInstance'를 'Singleton :: instance ()'의 정적 멤버로 만들지 않겠습니까?
Martin York

@ 마틴-완료 RAII 잠금 클래스를 사용하면 훨씬 더 간단합니다.
Michael Burr

10

다음 표준 (섹션 6.7.4)을 살펴보면 정적 로컬 초기화가 스레드로부터 어떻게 안전한지 설명합니다. 따라서 표준 섹션이 널리 구현되면 Meyer의 싱글 톤이 선호되는 구현이 될 것입니다.

나는 이미 많은 답변에 동의하지 않습니다. 대부분의 컴파일러는 이미 이런 식으로 정적 초기화를 구현합니다. 눈에 띄는 예외는 Microsoft Visual Studio입니다.


6

정답은 컴파일러에 따라 다릅니다. 그것은 결정할 수 있도록 그것을에서는 스레드; "자연스럽게"스레드 세이프가 아닙니다.


5

다음 구현 [...] 스레드는 안전한가요?

대부분의 플랫폼에서 스레드 안전하지 않습니다. (C ++ 표준은 스레드에 대해 알지 못하므로 법적으로 법적 여부를 말하지 않는다는 일반적인 고지 사항을 추가하십시오.)

그렇지 않다면 왜 [...]?

그렇지 않은 이유는 둘 이상의 스레드가 동시에 s'생성자를 실행하는 것을 방해하지 않기 때문입니다 .

스레드를 안전하게 만드는 방법?

Scott Meyers와 Andrei Alexandrescu의 "C ++ 및 이중 검사 잠금 위험" 은 스레드 안전 싱글 톤 주제에 대한 훌륭한 논문입니다.


2

MSalters가 말했듯이 : 사용하는 C ++ 구현에 달려 있습니다. 설명서를 확인하십시오. 다른 질문은 "그렇지 않다면 왜?" -C ++ 표준은 아직 스레드에 대해서는 언급하지 않았습니다. 그러나 다가오는 C ++ 버전은 스레드를 인식하고 정적 로컬의 초기화는 스레드로부터 안전하다는 것을 명시합니다. 두 스레드가 그러한 함수를 호출하면 한 스레드는 초기화를 수행하고 다른 스레드는 완료를 차단 및 기다립니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.