그래서 여러 스레드가 지연 생성 된 싱글 톤을 초기화하지 못하도록하는 C++ 이중 검사 잠금이 깨 졌다고 주장하는 기사가 많이 있습니다. 보통 이중 확인 잠금 코드는 다음과 같이 읽습니다이중 확인 잠금에 대한이 수정 프로그램의 문제점은 무엇입니까?
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
public:
static singleton & instance()
{
static singleton* instance;
if(!instance)
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
}
return *instance;
}
};
문제는 분명히 선 할당 인스턴스 - 컴파일러가 포인터를 할당하거나 포인터를 설정 한 후 객체를 할당하고 무료입니다 할당되고 할당됩니다. 후자의 경우는 관용구를 깨뜨린다. 하나의 쓰레드는 메모리를 할당하고 포인터를 할당하지만 싱글 톤의 생성자를 실행하기 전에 실행하지 않는다. 그런 다음 두 번째 쓰레드는 인스턴스가 null이 아니며 그것을 반환하려고 시도한다. 비록 그것이 아직 구축되지 않았지만.
I saw a suggestion 스레드 로컬 부울을 사용하고 instance
대신이를 확인하십시오. 이런 식으로 뭔가 :
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
static boost::thread_specific_ptr<int> _sync_check;
public:
static singleton & instance()
{
static singleton* instance;
if(!_sync_check.get())
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
// Any non-null value would work, we're really just using it as a
// thread specific bool.
_sync_check = reinterpret_cast<int*>(1);
}
return *instance;
}
};
약간의 성능 저하를 수반하지만, 모든 전화를 잠금으로 여전히 거의 그렇게 나쁘지 각 스레드가 인스턴스가 한 번 생성 된 경우 확인 끝납니다 만, 그 후 정지 이런 식으로. 하지만 로컬 스태커를 방금 사용하면 어떨까요? :
class singleton {
private:
singleton(); // private constructor so users must call instance()
static boost::mutex _init_mutex;
public:
static singleton & instance()
{
static bool sync_check = false;
static singleton* instance;
if(!sync_check)
{
boost::mutex::scoped_lock lock(_init_mutex);
if(!instance)
instance = new singleton;
sync_check = true;
}
return *instance;
}
};
왜이 기능이 작동하지 않습니까? sync_check가 다른 스레드에 할당 될 때 한 스레드가 sync_check를 읽을지라도 가비지 값은 여전히 0이 아니므로 참입니다. This Dr. Dobb's article은 명령을 재정렬하는 것보다 컴파일러와의 전투에서 결코 이기지 않으므로 잠 가야한다고 주장합니다. 그래서 어떤 이유로 든 작동하지 않아야한다고 생각 하죠.하지만 그 이유를 알 수는 없어요. 시퀀스 포인트에 대한 요구 사항이 Dr. Dobb의 기사만큼이나 손실된다고 생각하면 왜 코드가 자물쇠 앞에 정렬되지 않았는지 이해할 수 없습니다. C++ 멀티 스레딩이 깨진 것입니다.
로컬 변수이기 때문에 sync_check를 특별히 재정렬 할 수있는 컴파일러를 볼 수있을 것 같아요 (정적인데도 참조 또는 포인터를 반환하지는 않습니다) -하지만이 경우 대신 정적 멤버 (효과적으로 글로벌)로 만들어 해결할 수 있습니다.
이렇게 작동합니까, 그렇지 않습니까? 왜?
문제는 개체가 할당되기 전에가 아니라 생성자가 실행 (또는 완료)되기 전에 변수를 할당 할 수 있다는 것입니다. – kdgregory
감사합니다. 나는 경쟁 조건을 완전히 망각했다. –
네, 맞습니다, 현재 C++은 실제로 "멀티 쓰레드 깨진 마침표"입니다. 표준으로 만 고려할 때. 보통 컴파일러 벤더들은 이것에 대한 방법을 제공합니다. 따라서 실용적인 결과는 그렇게 끔찍한 것이 아닙니다. – Suma