web-dev-qa-db-de.com

Thread Safe C # Singleton Pattern

Ich habe einige Fragen zum Singleton-Muster, wie hier dokumentiert: http://msdn.Microsoft.com/en-us/library/ff650316.aspx

Der folgende Code ist ein Auszug aus dem Artikel:

using System;

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new object();

   private Singleton() {}

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

         return instance;
      }
   }
}

Gibt es im obigen Beispiel insbesondere eine Notwendigkeit, die Instanz zweimal vor und nach der Sperre mit Null zu vergleichen? Ist das notwendig? Warum nicht zuerst die Sperre durchführen und den Vergleich durchführen?

Gibt es ein Problem, wenn Sie Folgendes vereinfachen?

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

         return instance;
      }
   }

Ist das Durchführen des Schlosses teuer?

65
Wayne Phipps

Das Durchführen der Sperre ist furchtbar teuer im Vergleich zur einfachen Zeigerprüfung instance != null.

Das Muster, das Sie hier sehen, heißt doppelt geprüftes Sperren . Ihr Zweck besteht darin, den kostspieligen Sperrvorgang zu vermeiden, der nur einmal benötigt wird (beim ersten Zugriff auf den Singleton). Die Implementierung ist so, da auch sichergestellt werden muss, dass bei der Initialisierung des Singleton keine Fehler aufgrund von Thread-Race-Bedingungen auftreten.

Stellen Sie sich das so vor: Eine nackte null-Prüfung (ohne eine lock) gibt Ihnen garantiert nur dann eine korrekt verwendbare Antwort, wenn diese Antwort "ja, das Objekt ist bereits konstruiert" ist. Wenn die Antwort jedoch "noch nicht erstellt" ist, haben Sie nicht genügend Informationen, da Sie eigentlich wissen wollten, dass es "noch nicht erstellt ist und kein anderer Thread beabsichtigt, sie in Kürze zu konstruieren". Sie verwenden also die äußere Prüfung als sehr schnellen ersten Test und initiieren die korrekte, fehlerfreie aber "teure" Prozedur (Sperre dann prüfen) nur, wenn die Antwort "nein" lautet.

Die obige Implementierung ist für die meisten Fälle ausreichend, aber zu diesem Zeitpunkt ist es eine gute Idee, Jon Skeets Artikel über Singletons in C # zu lesen, in dem auch andere Alternativen bewertet werden.

107
Jon

Die faule Version:

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

    public static Singleton Instance => lazy.Value;
}

Benötigt .NET 4 und C # 6.0 (VS2015) oder neuer.

22
andasa

Eine Sperre durchführen: Ziemlich billig (noch teurer als ein Nulltest).

Eine Sperre ausführen, wenn ein anderer Thread es hat: Sie bekommen die Kosten für das, was sie noch tun müssen, während sie gesperrt werden.

Eine Sperre ausführen, wenn ein anderer Thread es hat, und Dutzende anderer Threads warten ebenfalls darauf: Krüppel.

Aus Leistungsgründen möchten Sie immer Sperren haben, die ein anderer Thread wünscht, und zwar in kürzester Zeit.

Natürlich ist es einfacher, über "breite" Schlösser zu entscheiden, als eng, also lohnt es sich, mit ihnen zu beginnen und nach Bedarf zu optimieren. Es gibt jedoch Fälle, in denen wir aus Erfahrung und Bekanntheit lernen, wo ein engeres Muster zum Muster passt.

(Übrigens, wenn Sie möglicherweise nur private static volatile Singleton instance = new Singleton() verwenden können, oder wenn Sie möglicherweise nicht einfach Singletons verwenden, sondern stattdessen eine statische Klasse verwenden, sind beide in Bezug auf diese Bedenken besser.).

9
Jon Hanna

Der Grund ist Leistung. Wenn instance != null (was immer der Fall sein wird, außer beim allerersten Mal), besteht keine Notwendigkeit, eine kostspielige lock auszuführen: Zwei Threads, die gleichzeitig auf das initialisierte Singleton zugreifen, werden unnötig synchronisiert.

6
Heinzi

In fast allen Fällen (dh allen Fällen außer den allerersten) ist instance nicht null. Der Erwerb einer Sperre ist teurer als eine einfache Prüfung. Daher ist das Überprüfen von instance vor dem Sperren eine nette und kostenlose Optimierung.

Dieses Muster wird als doppelt geprüftes Sperren bezeichnet: http://en.wikipedia.org/wiki/Double-checked_locking

4
Kevin Gosse

Jeffrey Richter empfiehlt folgendes:



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

        private Singleton()
        {
        }

        public static Singleton Instance
        {
            get
            {
                if(instance != null) return instance;
                Monitor.Enter(s_lock);
                Singleton temp = new Singleton();
                Interlocked.Exchange(ref instance, temp);
                Monitor.Exit(s_lock);
                return instance;
            }
        }
    }

3

Abhängig von den Anforderungen Ihrer Anwendung können Sie eifrig die fadensichere Singleton-Instanz erstellen. Hierbei handelt es sich um prägnanten Code, auch wenn ich @ Lazas Version bevorzugen würde.

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

    private Singleton() { }

    public static Singleton Instance()
    {
        return instance;
    }
}
0
Brian Ogden

Eine andere Version von Singleton, bei der die folgende Codezeile die Singleton-Instanz zum Zeitpunkt des Anwendungsstarts erstellt.

private static readonly Singleton singleInstance = new Singleton();

Hier kümmert sich CLR (Common Language Runtime) um Objektinitialisierung und Threadsicherheit. Das heißt, wir müssen keinen Code explizit schreiben, um die Threadsicherheit für eine Multithread-Umgebung zu behandeln.

"Das Laden von Eager im Singleton-Entwurfsmuster ist kein Vorgang, bei dem das Singleton-Objekt zum Zeitpunkt des Starts der Anwendung initialisiert werden muss, sondern nur bei Bedarf, um es für die zukünftige Verwendung im Speicher bereit zu halten."

public sealed class Singleton
    {
        private static int counter = 0;
        private Singleton()
        {
            counter++;
            Console.WriteLine("Counter Value " + counter.ToString());
        }
        private static readonly Singleton singleInstance = new Singleton(); 

        public static Singleton GetInstance
        {
            get
            {
                return singleInstance;
            }
        }
        public void PrintDetails(string message)
        {
            Console.WriteLine(message);
        }
    }

von main:

static void Main(string[] args)
        {
            Parallel.Invoke(
                () => PrintTeacherDetails(),
                () => PrintStudentdetails()
                );
            Console.ReadLine();
        }
        private static void PrintTeacherDetails()
        {
            Singleton fromTeacher = Singleton.GetInstance;
            fromTeacher.PrintDetails("From Teacher");
        }
        private static void PrintStudentdetails()
        {
            Singleton fromStudent = Singleton.GetInstance;
            fromStudent.PrintDetails("From Student");
        }
0
Jaydeep Shil