싱글톤 패턴 (Singleton Pattern) [첫번째]

1. 객체 생성을 위한 디자인 패턴

  • Singleton(싱글톤) 패턴
  • Abstract Factory 패턴
  • Builder 패턴
  • Factory Method 패턴
  • Prototype 패턴

오늘은 객체 생성을 위한 패턴들 중에서 Singleton Pattern (싱글톤 패턴)에 대해 알아보도록 하죠.
싱글톤 패턴은 객체를 생성하는 방법론 중에서 가장 기본적이고 중요한 패턴입니다.

Singleton 이라는 말은 1개, 단일 개체를 의미합니다.

얼마전에 SBS 스페셜에서 싱글톤이라는 제목으로 혼자 사는 사람들에 대한 다큐를 본 적이 있는데요. 여기에서도 같은 의미입니다. ^^
하나뿐인 객체, 혼자 사는 외로운 객체, 세상에서 단 하나뿐인 특별한 객체에 대한 이야기입니다. ㅎㅎ

객체가 단 하나 뿐이어야 하는 상황... 어떤 상황일지 먼저 상상의 날개를 펼쳐봅시다.

어떤 특별한 서비스를 수행하는 프로세스가 있는데, 이 프로세스는 멀티스레드(multi-threaded) 구조라고 상상해 보죠.
그런데, 각 스레드들은 생성 후 너도나도 강아지를 좋아해서 puppy_class 라는 객체를 생성하는 공통모듈을 수행하도록 구현되어 있습니다.
아마도 이런 상황에서는 각 스레드가 생성될 때마다 강아지 객체가 하나씩 생성되겠죠 ?
그런데, 스레드를 수백, 수천개씩 생성해야하는 서비스도 있습니다.
만약 puppy_class 객체가 메모리를 꽤 많이 사용하는데, 실질적으로는 여러 객체를 생성할 필요없이 모든 스레드가 공유하는 객체 하나만 필요한 경우일 때는 몹시 낭비가 아닐까요 ?

이러한 상황은 멀티스레드가 아닌 단일스레드 환경에서도 얼마든지 발생할 수 있습니다.
(구체적인 상황은 독자의 상상에 맡겨봅니다.)

객체가 하나만 있어도 되는데 수천개씩 생성하는 code는 분명 효율적이지는 않을 것입니다.
메모리가 충분하지 않은 상황에서는 더더욱 좋지 않은 code 임은 분명합니다.

그렇다면, 이런 경우 class 를 어떻게 설계해야 할까요 ?
앞으로 우리는 class 를 설계할 때 제가 좋아하는 동물인 강아지(puppy)를 가지고 만들겁니다. ^^
(Ubuntu 11.10 에서 시험했습니다.)

다음의 code 를 수행해 봅시다.
스레드 10 개를 차례대로 생성하면 해당 스레드는 곧 바로 강아지 객체 하나씩을 생성하는 코드입니다.

#include stdio.h
#include unistd.h
#include pthread.h

class puppy_class
{
private:
public:
    puppy_class(int arg)  { printf("puppy_no : %d\n", arg); }
};

void *thread_func(void *arg)
{
    int puppy_no = *(int *)arg;
    puppy_class puppy(puppy_no);  /* 강아지 객체 생성 */
}

int main()
{
    int  i;
    pthread_t tid[10];

    for(i=0; i<10; i++)
    {
        /* 스레드 생성 */
        pthread_create(&tid[i], NULL, thread_func, &i);
    }
}

이제 컴파일 후 실행을 해볼까요 ?

$ g++ puppy_class.cpp -lpthread
$ ./a.out
puppy_no : 0
puppy_no : 1
puppy_no : 2
puppy_no : 3
puppy_no : 4
puppy_no : 5
puppy_no : 6
puppy_no : 7
puppy_no : 8
puppy_no : 9

위쪽에 설명했던대로 강아지 객체가 10 개가 생성되었네요. ^^
그럼 위의 코드를 아래와 같이 수정해보죠.

#include stdio.h
#include unistd.h
#include pthread.h
class puppy_class
{
private:
    static puppy_class *instance;  /* 객체를 가리키는 static 포인터 */
public:
    puppy_class(int arg)  { printf("puppy_no : %d\n", arg); }
    static puppy_class *get_instance(int arg)
    {
        if(instance == NULL)
        {
            /* 객체가 아직 생성되지 않았을 경우에만 객체 생성 */
            instance = new puppy_class(arg);
        }
        return instance;
    }
};

puppy_class *puppy_class::instance = NULL;

void *thread_func(void *arg)
{
    int puppy_no = *(int *)arg;
    /* get_instance 멤버함수를 이용하여 생성한 객체를 이용 */
    puppy_class *puppy = puppy_class::get_instance(puppy_no);
}

int main()
{
    int  i;
    pthread_t tid[10];

    for(i=0; i<10; i++)
    {
        pthread_create(&tid[i], NULL, thread_func, &i);
    }
}

실행해볼까요 ?

$ g++ puppy_class.cpp -lpthread
$ ./a.out
puppy_no : 0

스레드는 10 개를 생성했지만 강아지 객체는 단 하나만 생성됐네요.
위의 빨간색으로 된 변경된 코드를 보면 이해가 될 것입니다.

thread_func 내에서 객체를 얻고 싶을 때는 이전과는 다르게 get_instance() 라는 함수를 이용하였습니다. get_instance 에서는 instance 가 NULL 일 때만 new 를 통해 새로운 객체를 생성하는군요.

puppy_class 형 객체포인터인 instance 는 static 으로 선언되어 모든 객체가 공통으로 사용하도록 하고 있네요.

한마디로 instance 변수를 확인해봤더니 아직 객체를 가리키고 있지 않은 상태일 때만 한번 객체를 생성해주고 해당 객체를 리턴해주는 방법입니다. 이렇게 하면 스레드 수가 아무리 많아도 강아지 객체는 하나만 생성될테니 아주 편리하겠네요. ^^

그런데, 위의 코드는 멀티스레드 환경에서 아주 치명적인 약점이 있습니다.
무엇일까요 ?  이는 다음 강의에서 살펴보도록 하겠습니다.

힌트는 if(instance == NULL) 코드에 있고, 아주 많은 스레드가 경쟁하는 환경에서 약점이 드러납니다.

그럼...

You may also like...

0 0 votes
Article Rating
Subscribe
Notify of
guest
4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
패턴을알자

작성하신 글을 모두 보았습니다.
정말 많은 도움이 되었습니다
감사합니다~~

구름과비21

감사합니다.
요즘 제가 블로깅에 좀 소홀하네요.
조만간 다시 불붙으면 폭풍 블로깅을 하게 될수도 있을 것 같습니다.
다시 찾아주세요. ^^

패턴을Ꮊ

작성하신 글을 모두 보았습니다.
정말 많은 도움이 되었습니다
감사합니다~~

구름과ጆ

감사합니다.
요즘 제가 블로깅에 좀 소홀하네요.
조만간 다시 불붙으면 폭풍 블로깅을 하게 될수도 있을 것 같습니다.
다시 찾아주세요. ^^

4
0
Would love your thoughts, please comment.x
()
x