본문 바로가기
게임 서버/C#

[c#/디자인패턴] - 싱글톤(Singleton) 패턴

by 얘리밍 2022. 12. 20.
320x100
728x90

 

📌싱글톤 패턴 : 클래스의 인스턴스를 단 하나만 생성하고자 할 때 사용한다 

 

 

게임 개발에서 주로 게임을 관리하는 매니저 계열의 클래스를 만들 때 적합하며, (딱 하나만 필요)

사운드를 재생하려 할 때, 관련된 정보를 어디에서나 알게 하여 누구나 재생할 수 있도록 하기 위해(전역적 접근) 

사용한다고 생각하면 된다. 

 

 

특징

  • Lazy 초기화(Lazy initialization) : 선언 즉시 인스턴스를 생성하는 것이 아니라, 처음 접근할 때 객체를 생성한다. 
  • 상속 가능 : 하위 클래스 오브젝트도 모두 싱글톤이 된다. 

 

 

 

1. 안전하지 않은 버전 - not thread-safe 

public class SingletonClass
{
    private SingletonClass() { }    //클래스의 외부에서 함부로 객체를 만들 수 없게 함 

    private static SingletonClass instance;    //객체 생성 이후 생성된 것만 꺼내서 사용함 
    public static SingletonClass Instance
    {
        get
        {
            if(instance == null) //인스턴스가 비어있는 경우에만 -> 생성되지 않았을 때, 
            { 
                instance = new SingletonClass();   //새 객체를 생성해서 instance에 변수에 넣어줌
            }
            return instance;
        }
    }
}

 

단 하나의 인스턴스만 생성해야 하므로 외부에서 객체를 생성할 수 없도록 private 접근 지정자를 설정한다. 

인스턴스가 null인지를 체크하여 비어있을 때만 새 객체를 만든다.

 

 

하지만 이 코드는 스레드로부터 안전하지 않다. 

서로다른 A,B 스레드가 모두 if(instance == null) 조건을 만족한다면, A와 B 모두 인스턴스를 생성하게 되어

싱글톤 패턴을 위반하기 때문이다. 

 

 

 

그렇다면 어떻게 해결할까🤔..?

 

  • lock을 사용하는 경우
  • lock을 사용하지 않는 경우
  • lazy를 활용하는 경우

 

 

 

 

2. 락(lock)을 사용한 싱글톤 - simple thread-safety

 

public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}

 

lock을 사용하면 인스턴스를 생성하기 전에 인스턴스가 미리 생성되었는 지 여부를 확인한다.

A 스레드가 먼저 진입하여 인스턴스를 생성하였다면, B 스레드가 그 다음으로 진입할 때 if(instance==null)가 false이기 때문에 인스턴스가 또 생성되지 않는다. 

 

 

 

 

3. lock을 사용하지 않지만 - thread-safe 한, 약간 lazy 한 

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    static Singleton()
    {
    }

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

 

static 생성자는 클래스의 인스턴스가 생성되거나 정적 멤버가 참조될 때만 실행되고 앱 도메인당 한 번만 실행되도록 지정된다. lock을 사용하는 것 보다는 성능면에서 더 빠르다. 

 

 

 

 

4. 완전한 lazy 인스턴스화

public sealed class Singleton
{
    private Singleton()
    {
    }

    public static Singleton Instance { get { return Nested.instance; } }

    private class Nested
    {
        static Nested()
        {
        }
        internal static readonly Singleton instance = new Singleton();
    }
}

 

 

 

 

5. using .NET 4's Lazy<T> type - 제네릭 사용

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

 

 

 

 

3번의 ( lock을 사용하지 않지만 - thread-safe 한, 약간 lazy 한 )의 방법을 가장 추천한다고 한다. 

혹은 5번..

 

싱글톤 사용이 마냥 좋은 것은 아니다. 단점 또한 존재한다. 

 

 

 

💥 단점 

  • 정적 변수를 사용한다 ➡ 모든 클래스에서 접근할 수 있기 때문에, 전혀 상관 없는 클래스와 결합(coupling) 생김 
  • 게임 플레이에 있어서 게으른 초기화는 화면 프레임이 떨어지거나 화면 끊김 현상을 발생시킬 수 있다. 

 

 

 

 

자세한 정리와 원글은

참고 : https://dev-nicitis.tistory.com/4

https://csharpindepth.com/articles/singleton

728x90
반응형

'게임 서버 > C#' 카테고리의 다른 글

[c#/자료구조] 배열(Array)  (0) 2022.12.06