
// header
class singleton
{
public:
static singleton* instance();
private:
singleton();
singleton(const singleton&);
singleton& operator=(const singleton&);
~singleton();
static singleton* instance_;
};
// implementation
singleton* singleton::instance_ = NULL;
singleton* singleton::instance()
{
if( instance_ == NULL )
{
instance_ = new singleton;
}
return instance_;
}
singleton을 구현한다면, 아마도 위와 같은 idiom을 따를 것이다①. 그리고 이미 잘 알려진대로 멀티쓰레드 환경에서 이 구현은 문제점을 가지고 있다.
singleton* singleton::instance()
{
if( instance_ == NULL )
{ <- shit!
instance_ = new singleton;
}
return instance_;
}
만약 A와 B 쓰레드가 있는데 A쓰레드가 instance함수에 진입해서 <-shit!이라고 표시된 곳에서 suspend된다면, B 쓰레드가 instance()함수에 진입해서 instance_가 아직 NULL이기때문에 객체를 초기화하고 그 주소를 반환할 것이다. 이후 A가 resume되서 <-shit이후 구문을 실행하기 때문에 당연히 또 객체를 초기화하고 그 주소를 반환할 것이다.
인스턴스가 두 개나 생기다니, 뭐가 singleton이란 말인가? 가상의 인물 tk군은 싱글쓰레드 환경에서 잘 작동하던 singleton 클래스를 멀티쓰레드 환경에서도 잘 작동하도록 하기 위해서 instance함수에 lock객체를 추가한다.
singleton* singleton::instance()
{
lock guard;
if( instance_ == NULL )
{
instance_ = new singleton;
}
return instance_;
}
휴, 이제 하나의 인스턴스만 생긴다. 하지만 instance_가 한번 초기화되고나면 바로 그 주소를 반환하면되는데, instance_가 초기화된 이후에도 어떤 쓰레드가 이미 선점하고 있으면 대기를 해야하는 상황이니, 성능저하가 이만저만이 아니다.
그래서 tk군은 instance_객체를 초기화할 때만 보호가 필요하므로, lock객체의 생성 위치를 변경하면 문제가 해결될 것이라 생각하고 lock객체의 생성 위치를 바꾼다.
singleton* singleton::instance()
{
if( instance_ == NULL )
{
lock guard; <- in series
instance_ = new singleton;
}
return instance_;
}
그런데 만약 A쓰레드가 lock을 걸고 초기화를 하기전에 B쓰레드가 이미 if문 블럭안으로 들어가서 대기한다면, 처음 구현이랑 똑같은 상황이다. tk군 바보짓했다.
잘생각해보면, lock객체로 인해서 <-in series부터는 순차적인 실행이된다. 그러므로 A,B쓰레드 중 누가되었건 lock을 획득했다면, 그 뒤에 대기하고 있는 쓰레드는 객체가 생성된 이후에 진입이 허용될 것이다. 그러면 그 곳에서 한번 더 instance_가 NULL인지 비교한다면 instance_가 두 번 초기화되지 않을 것이다.
아하! tk군이 위의 생각대로 함수를 다시 작성한다.
singleton* singleton::instance()
{
if( instance_ == NULL )
{
lock guard; <- in series
if( instance_ == NULL ) <- 2nd test
instance_ = new singleton;
}
return instance_;
}
A쓰레드가 lock을 하고 2nd test를 하면 당연히 instance_는 NULL이므로 초기화한다. 그 다음 대기하고 있던 B쓰레드가 2nd test를 수행하면 이미 instance_는 NULL이 아니므로 두 번 초기화되지 않는다.
휴 문제 해결~~~
지금까지 GoF책②에 소개된 singleton pattern이 멀티쓰레드 환경에서 발생하는 문제점때문에 그 해결 방법으로 Douglas C. Schmidt에 의해서 소개된 Double-Checked Locking Pattern(DCLP)③에 대한 이야기였다.
다음에는 컴파일러들이 CPU아키텍쳐에 최적화된 코드를 생성하다보니 발생하는 DCLP의 문제점과 간단한 해결 방법에 대한 이야기이다.
각주
펼쳐두기..
댓글 없음:
댓글 쓰기