Thread-sicheres C # Singleton-Muster

79

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

Muss im obigen Beispiel die Instanz vor und nach der Sperre zweimal mit null verglichen werden? Ist das notwendig? Warum nicht zuerst die Sperre durchführen und den Vergleich durchführen?

Gibt es ein Problem bei der Vereinfachung auf Folgendes?

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

         return instance;
      }
   }

Ist das Durchführen des Schlosses teuer?

Wayne Phipps
quelle
17
Nebenbei
Arran
fauler statischer Init wäre vorzuziehen ...
Mitch Wheat
1
Ich habe auch andere Beispiele mit Erklärungen hier: csharpindepth.com/Articles/General/Singleton.aspx
Serge Voloshenko
Genau die gleiche Frage hier für die Java-Welt.
RBT

Antworten:

133

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

Das Muster, das Sie hier sehen, wird als doppelt geprüftes Sperren bezeichnet . Ihr Zweck ist es, die teure Sperroperation zu vermeiden, die nur einmal benötigt wird (wenn der Singleton zum ersten Mal aufgerufen wird). Die Implementierung ist so, weil sie auch sicherstellen muss, dass bei der Initialisierung des Singletons keine Fehler aufgrund von Thread-Race-Bedingungen auftreten.

Stellen Sie sich das so vor: Ein bloßer nullScheck (ohne a lock) gibt Ihnen garantiert nur dann eine korrekte verwendbare Antwort, wenn diese Antwort "Ja, das Objekt ist bereits konstruiert" lautet. Wenn die Antwort jedoch "noch nicht erstellt" lautet, haben Sie nicht genügend Informationen, da Sie eigentlich wissen wollten, dass sie "noch nicht erstellt wurde und kein anderer Thread beabsichtigt, sie in Kürze zu erstellen ". Sie verwenden also die äußere Prüfung als sehr schnellen ersten Test und leiten das richtige, fehlerfreie, aber "teure" Verfahren (Sperren und Prüfen) nur dann ein, wenn die Antwort "Nein" lautet.

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

Jon
quelle
1
Vielen Dank für eine informative Antwort mit nützlichen Links. Sehr geschätzt.
Wayne Phipps
Der doppelt überprüfte Verriegelungslink funktioniert nicht mehr.
El Mac
Es tut mir leid, ich meinte den anderen.
El Mac
1
@ElMac: Skeets Website ist am Geldautomaten nicht verfügbar. Sie wird zu gegebener Zeit wieder verfügbar sein. Ich werde es mir merken und sicherstellen, dass der Link immer noch funktioniert, wenn er auftaucht, danke.
Jon
3
Seit .NET 4.0 Lazy<T>macht das diesen Job einfach perfekt.
Ilyabreev
34

Die Lazy<T>Version:

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

    public static Singleton Instance
        => lazy.Value;

    private Singleton() { }
}

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

andasa
quelle
Ich erhalte die Meldung "System.MissingMemberException: 'Der träge initialisierte Typ hat keinen öffentlichen, parameterlosen Konstruktor.'" Mit diesem Code in .Net 4.6.1 / C # 6.
ttugates
@ttugates, du hast recht, danke. Code aktualisiert mit einem Value Factory-Rückruf für das Lazy-Objekt.
Andasa
14

Sperren durchführen: Ziemlich billig (immer noch teurer als ein Nulltest).

Durchführen einer Sperre, wenn ein anderer Thread sie hat: Sie erhalten die Kosten für alles, was sie beim Sperren noch tun müssen, zusätzlich zu Ihrer eigenen Zeit.

Durchführen einer Sperre, wenn ein anderer Thread sie hat und Dutzende anderer Threads ebenfalls darauf warten: Verkrüppeln.

Aus Leistungsgründen möchten Sie immer Sperren haben, die ein anderer Thread wünscht, und zwar für den kürzestmöglichen Zeitraum.

Natürlich ist es einfacher, über "breite" Schlösser nachzudenken als über schmale, daher lohnt es sich, mit ihnen zu beginnen und sie nach Bedarf zu optimieren, aber es gibt einige Fälle, die wir aus Erfahrung und Vertrautheit lernen, in denen ein schmalerer zum Muster passt.

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

Jon Hanna
quelle
1
Ich mag dein Denken hier wirklich. Es ist eine großartige Möglichkeit, es zu betrachten. Ich wünschte, ich könnte zwei Antworten akzeptieren oder +5 diese, vielen Dank
Wayne Phipps
2
Eine Konsequenz, die wichtig wird, wenn es darum geht, die Leistung zu betrachten, ist der Unterschied zwischen gemeinsam genutzten Strukturen, die gleichzeitig getroffen werden könnten , und solchen, die dies tun werden . Manchmal erwarten wir nicht, dass ein solches Verhalten häufig auftritt, aber es könnte sein, also müssen wir sperren (es dauert nur einen Fehler, um alles zu ruinieren). In anderen Fällen wissen wir, dass viele Threads tatsächlich gleichzeitig dieselben Objekte treffen. Wieder andere Zeiten hatten wir nicht mit viel Parallelität gerechnet, aber wir haben uns geirrt. Wenn Sie die Leistung verbessern müssen, haben diejenigen mit viel Parallelität Vorrang.
Jon Hanna
7

Der Grund ist die Leistung. Wenn instance != null(was bis auf das allererste Mal immer der Fall sein wird), ist dies nicht kostspielig lock: Zwei Threads, die gleichzeitig auf den initialisierten Singleton zugreifen, würden unnötig synchronisiert.

Heinzi
quelle
4

In fast allen Fällen (dh in allen Fällen außer den allerersten) instancewird nicht null sein. Das Erwerben einer Sperre ist teurer als eine einfache Überprüfung. instanceDaher ist es eine schöne und kostenlose Optimierung, den Wert vor dem Sperren einmal zu überprüfen .

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

Kevin Gosse
quelle
3

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

Yauheni Charniauski
quelle
Ist es nicht dasselbe, die Instanzvariable flüchtig zu machen?
О Г И І І О
1

Sie können eifrig eine thread-sichere Singleton-Instanz erstellen, abhängig von Ihren Anwendungsanforderungen. Dies ist prägnanter Code, obwohl ich die faule Version von @ andasa bevorzugen würde.

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

    private Singleton() { }

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

Dies wird als doppelt überprüfter Sperrmechanismus bezeichnet. Zuerst prüfen wir, ob die Instanz erstellt wurde oder nicht. Wenn nicht, synchronisieren wir nur die Methode und erstellen die Instanz. Dadurch wird die Leistung der Anwendung drastisch verbessert. Das Durchführen der Sperre ist schwer. Um die Sperre zu vermeiden, müssen wir zuerst den Nullwert überprüfen. Dies ist auch threadsicher und der beste Weg, um die beste Leistung zu erzielen. Bitte schauen Sie sich den folgenden Code an.

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

    public static Singleton GetInstance
    {
        get
        {
            if (instance == null)
            {
                lock (Instancelock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}
Pranaya Rout
quelle
0

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 die Objektinitialisierung und die Thread-Sicherheit. Das bedeutet, dass wir keinen Code explizit schreiben müssen, um die Thread-Sicherheit für eine Multithread-Umgebung zu gewährleisten.

"Das eifrige Laden im Singleton-Entwurfsmuster ist kein Prozess, bei dem wir das Singleton-Objekt zum Zeitpunkt des Anwendungsstarts und nicht bei Bedarf initialisieren und im Speicher bereithalten müssen, um es in Zukunft verwenden zu können."

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");
        }
Jaydeep Shil
quelle
Schöne Alternative, beantwortet aber nicht die Frage, die sich auf die Sperrprüfung in der in der Frage erwähnten spezifischen Implementierung bezog
Wayne Phipps
nicht direkt, kann aber als Alternative "Thread Safe C # Singleton Pattern" verwendet werden.
Jaydeep Shil