>  기사  >  백엔드 개발  >  C# 싱글톤 패턴 구현 및 성능 비교 예시

C# 싱글톤 패턴 구현 및 성능 비교 예시

黄舟
黄舟원래의
2018-05-16 17:08:521982검색

이 글에서는 주로 C# 싱글톤 모드의 구현과 성능 비교에 대한 관련 정보를 소개합니다. 6가지 구현 방법이 필요하신 분들은 참고하시면 됩니다.

Introduction

싱글케이스는 A클래스만 지칭합니다. 하나의 인스턴스(C#에서는 각 AppDomain에 하나의 인스턴스만 가질 수 있는 클래스)를 갖는 것은 소프트웨어 엔지니어링에서 가장 일반적으로 사용되는 패턴 중 하나입니다. 이 클래스를 사용하면 이전에 생성된 인스턴스만 사용할 수 있으며 새 인스턴스를 생성할 수 없습니다. 일반적으로 싱글톤은 처음 사용할 때 생성됩니다. C#의 여러 싱글톤 구현 방법이 도입되었으며 스레드 안전성과 성능 차이가 있습니다.

싱글턴을 구현하는 방법에는 여러 가지가 있지만 가장 간단한 구현(지연 로딩 없음, 스레드 안전하지 않음)부터 시작하여 지연 로딩, 스레드 안전 및 효율적인 구현에 이르기까지 다양합니다. 모두 기본적인 공통점이 있습니다:

  • 싱글턴 클래스는 모두 인수 없는 전용 생성자만 가집니다.

  • 클래스 선언은 봉인되어 있습니다(필수는 아님)

  • 클래스에 다음을 보유하는 정적 변수가 있습니다. 생성된 인스턴스에 대한 참조

  • 싱글턴 클래스는 생성된 인스턴스에 대한 참조를 반환하는 정적 메서드 또는 속성을 제공합니다(예: GetInstance)

여러 구현

스레드가 아닌 하나의 안전성

//Bad code! Do not use!
public sealed class Singleton
{
  private static Singleton instance = null;
  private Singleton()
  {
  }

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

이 메서드는 스레드로부터 안전하지 않습니다. if(instance == null)가 동시에 실행되고 두 개의 서로 다른 인스턴스가 생성되면 새로 생성된 인스턴스가 새로 생성된 인스턴스를 대체하므로 이전에 얻은 참조가 발생합니다.

두 번째 간단한 스레드 안전 구현

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;
      }
    }
  }
}

이 버전은 인스턴스를 호출하기 전에 인스턴스에 잠금을 추가하므로 구현 1에서 스레드 충돌을 방지합니다. 처음부터 끝까지 하나의 인스턴스만 생성합니다. 그러나 인스턴스가 호출될 때마다 잠금이 사용되므로 잠금을 호출하는 데 비용이 많이 듭니다.

여기서는 새로운 개인 개체 인스턴스를 사용합니다. 잠재적인 위험을 초래할 수 있는 싱글톤을 직접 잠그는 대신 잠금 작업을 구현하기 위한 자물쇠입니다. 이론적으로 모든 코드에서 직접 잠그면 성능 문제가 발생하고 교착 상태가 발생할 수도 있습니다.

참고: C#에서는 동일한 스레드가 객체를 잠글 수 있지만, 서로 다른 스레드가 동시에 잠기면 스레드 대기가 발생하거나 심각한 교착 상태가 발생할 수 있으므로 잠금을 사용할 때는 전용 변수를 잠그도록 하십시오.

3중 검증 스레드 안전성 구현

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

  Singleton()
  {
  }

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

이 구현은 스레드 안전성을 보장하는 동시에 인스턴스가 호출될 때마다 잠금 작업을 방지하여 일정 시간.

그러나 이 구현에는 단점도 있습니다.

1 Java에서는 작동하지 않습니다. (구체적인 이유로 원문을 볼 수 있지만 여기서는 잘 이해가 되지 않습니다.)

2 프로그래머는 직접 구현할 때 실수를 저지르는 경향이 있습니다. 이 모드에서 코드를 직접 수정하는 경우 이중 확인 논리가 상대적으로 복잡하고 잘못된 사고로 인해 실수를 저지르기 쉽기 때문에 매우 주의해야 합니다.

잠금이 없는 네 가지 스레드로부터 안전한 구현

public sealed class Singleton
{
  //在Singleton第一次被调用时会执行instance的初始化
  private static readonly Singleton instance = new Singleton();

  //Explicit static consturctor to tell C# compiler 
  //not to mark type as beforefieldinit
  static Singleton()
  {
  }

  private Singleton()
  {
  }

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

이 구현은 매우 간단하고 잠금을 사용하지 않지만 여전히 스레드로부터 안전합니다. 여기서는 정적 읽기 전용 Singleton 인스턴스가 사용됩니다. Singleton이 처음 호출될 때 새 인스턴스가 생성됩니다. 새 인스턴스를 생성할 때 스레드 안전 보장은 .NET에서 직접 제어됩니다. 그리고 AppDomaing에서는 한 번만 생성됩니다.

이 구현에는 몇 가지 단점도 있습니다.

1 인스턴스가 생성되는 타이밍을 알 수 없으며 Singleton을 호출하면 인스턴스가 미리 생성됩니다.
2 정적 생성자의 순환 호출. A와 B라는 두 개의 클래스가 있고 B가 A의 정적 생성자에서 호출되고 A가 B의 정적 생성자에서 호출되는 경우 이 둘은 순환 호출을 형성하여 프로그램이 심각하게 중단될 수 있습니다.
3 Singleton이 처음 호출될 때 인스턴스가 생성되도록 하려면 Singleton 유형이 beforefieldinit 속성과 함께 자동으로 추가되지 않도록 Singleton의 정적 생성자를 수동으로 추가해야 합니다.
4readonly 속성은 런타임에 변경할 수 없습니다. 프로그램이 실행 중일 때 인스턴스를 삭제하고 새 인스턴스를 다시 만들어야 하는 경우 이 구현 방법을 만족할 수 없습니다.

Five의 완전 게으른 인스턴스화

public sealed class Singleton
{
  private Singleton()
  {
  }

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

  private class Nested
  {
    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Nested()
    {
    }

    internal static readonly Singleton instance = new Singleton();
  }
}

5번 구현은 4번 구현의 래퍼입니다. 이는 인스턴스가 Instance의 get 메소드에서만 호출되고 첫 번째 호출 전에만 초기화되도록 보장합니다. 지연 로딩을 보장하는 구현 4 버전입니다.

Six .NET4의 Lazy8742468051c85b06f0a0af9e3e506b5c 유형을 사용하세요

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()
  {
  }
}

.NET4 이상에서는 Lazy8742468051c85b06f0a0af9e3e506b5c를 지원하여 싱글톤의 스레드 안전성과 지연 로딩 특성을 보장합니다.

성능 차이

이전 구현에서는 스레드 안전성과 코드 지연 로딩을 강조했습니다. 그러나 실제 사용 시 싱글톤 클래스의 초기화에 시간이 많이 걸리지 않거나 초기화 시퀀스로 인해 버그가 발생하지 않는 경우에는 초기화에 소요되는 시간이 미미하므로 지연된 초기화는 불필요한 기능입니다.

실제 사용 시나리오에서 싱글톤 인스턴스가 자주 호출되는 경우(예: 루프에서) 스레드 안전성 보장으로 인한 성능 소비에 더 주목할 가치가 있습니다.

이러한 구현의 성능을 비교하기 위해 작은 테스트를 수행했습니다. 이 구현의 싱글톤을 9억 번 반복하고, 매번 인스턴스 메서드를 호출하여 count++ 작업을 수행하고, 백만 개마다 출력하고, 환경을 실행했습니다. MBP의 Mac용 Visual Studio입니다. 결과는 다음과 같습니다.


스레드 안전성 지연 로딩 테스트 실행 시간(ms)
구현 1 아니요 15532
업적 2 45803
3을 실현 15953
4를 실현 완전히는 아니고 14572
5 를 실현하는 중 14295
6개 구현 22875

테스트 방법이 엄격하지는 않지만 여전히 방법 2가 시간이 가장 많이 소요되는 것을 볼 수 있습니다. 매번 lock을 호출해야 하는데, 이는 다른 것보다 거의 3배나 많습니다. 두 번째 순위는 .NET Lazy 유형을 사용하는 구현으로, 이는 다른 것보다 약 절반 더 많습니다. 나머지 4개는 뚜렷한 차이가 없습니다.

요약

일반적으로 위에서 언급한 다양한 싱글톤 구현 방법은 오늘날의 컴퓨터 성능과 크게 다르지 않습니다. 특히 동시 호출량이 많은 인스턴스를 호출해야 하는 경우가 아니라면 성능을 고려해야 합니다. 질문.

일반 개발자의 경우 방법 2나 방법 6을 사용하여 싱글톤을 구현해도 충분합니다. 방법 4와 5는 C# 실행 프로세스에 대한 충분한 이해와 이를 구현할 때 어느 정도의 숙달이 필요합니다. 저장은 여전히 ​​제한되어 있습니다.

인용문

이 기사의 대부분은 내가 이해한 내용을 일부 추가하여 C#에서 싱글톤 패턴 구현에서 번역되었습니다. 이것은 내가 정적 읽기 전용 필드 초기화와 정적 생성자 초기화를 검색했을 때 본 것입니다. 여기에 있는 두 저자에게 감사를 표하고 싶습니다.

위 내용은 C# 싱글톤 패턴 구현 및 성능 비교 예시의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.