Best Practices für die .NET-Thread-Synchronisierung

Die Synchronisierung ist ein Konzept, mit dem verhindert wird, dass mehrere Threads gleichzeitig auf eine gemeinsam genutzte Ressource zugreifen. Sie können damit verhindern, dass mehrere Threads gleichzeitig die Eigenschaften oder Methoden eines Objekts aufrufen. Sie müssen lediglich den Codeblock synchronisieren, der auf die gemeinsam genutzte Ressource zugreift, oder die Aufrufe der Eigenschaften und Mitglieder des Objekts synchronisieren, sodass zu einem bestimmten Zeitpunkt nur ein Thread in den kritischen Abschnitt eintreten kann.

Dieser Artikel enthält eine Diskussion über die Konzepte in Bezug auf Synchronisation und Thread-Sicherheit in .Net und die damit verbundenen Best Practices.

Exklusives Schloss

Durch die exklusive Verriegelung wird sichergestellt, dass zu einem bestimmten Zeitpunkt nur ein Thread einen kritischen Abschnitt betreten kann. Sie müssen eine der folgenden Methoden verwenden, um exklusive Sperren in Ihrer Anwendung zu implementieren.

  • Sperre - Dies ist eine syntaktische Verknüpfung für die statischen Methoden der Monitor-Klasse und wird verwendet, um eine exklusive Sperre für eine gemeinsam genutzte Ressource zu erhalten
  • Mutex - ähnelt dem Schlüsselwort lock, kann jedoch über mehrere Prozesse hinweg verwendet werden
  • SpinLock - wird verwendet, um eine exklusive Sperre für eine gemeinsam genutzte Ressource zu erlangen, indem der Overhead für den Thread-Kontextwechsel vermieden wird

Sie können die statischen Methoden der Monitor-Klasse oder das Schlüsselwort lock verwenden, um die Thread-Sicherheit in Ihren Anwendungen zu implementieren. Sowohl die statischen Mitglieder der Monitor-Klasse als auch die Sperrschlüsselwörter können verwendet werden, um den gleichzeitigen Zugriff auf eine gemeinsam genutzte Ressource zu verhindern. Das Schlüsselwort lock ist nur eine Abkürzung zum Implementieren der Synchronisation. Wenn Sie jedoch komplexe Vorgänge in einer Multithread-Anwendung ausführen müssen, können die Methoden Wait () und Pulse () der Monitor-Klasse hilfreich sein.

Das folgende Codeausschnitt zeigt, wie Sie die Synchronisierung mithilfe der Monitor-Klasse implementieren können.

private static readonly object lockObj = new object();

        static void Main(string[] args)

        {

            Monitor.Enter(lockObj);

                       try

            {

               //Some code

            }

                  finally

            {

                Monitor.Exit(lockObj);

            }

        }

Der entsprechende Code mit dem Schlüsselwort lock sieht folgendermaßen aus:

    private static readonly object lockObj = new object();

        static void Main(string[] args)

        {  

            try

            {

                lock(lockObj)

                {

                    //Some code

                }             

            }

            finally

            {

                //You can release any resources here

            }

        }

Sie können die Mutex-Klasse nutzen, um eine prozessübergreifende Synchronisation zu implementieren. Beachten Sie, dass ähnlich wie bei der lock-Anweisung eine von einem Mutex erworbene Sperre nur von demselben Thread freigegeben werden kann, der zum Erfassen der Sperre verwendet wurde. Das Erfassen und Freigeben von Sperren mit Mutex ist vergleichsweise langsamer als mit der Anweisung lock.

Die Hauptidee hinter SpinLock besteht darin, die Kosten für den Kontextwechsel zwischen Threads zu minimieren. Wenn ein Thread einige Zeit warten oder drehen kann, bis er eine Sperre für eine gemeinsam genutzte Ressource erhält, kann der mit dem Kontextwechsel zwischen Threads verbundene Overhead vermieden werden . Wenn der kritische Abschnitt nur wenig Arbeit leistet, kann er ein guter Kandidat für ein SpinLock sein.

Nicht exklusives Schloss

Sie können die nicht exklusive Sperre nutzen, um die Parallelität zu begrenzen. Um nicht exklusive Sperren zu implementieren, können Sie eine der folgenden Optionen verwenden.

  • Semaphor - Wird verwendet, um die Anzahl der Threads zu begrenzen, die gleichzeitig auf eine gemeinsam genutzte Ressource zugreifen können. Im Wesentlichen wird es verwendet, um die Anzahl der Verbraucher für eine bestimmte gemeinsam genutzte Ressource gleichzeitig zu begrenzen.
  • SemaphoreSlim - eine schnelle, leichte Alternative zur Semaphore-Klasse zur Implementierung nicht exklusiver Sperren.
  • ReaderWriterLockSlim - Die ReaderWriterLockSlim-Klasse wurde in .Net Framework 3.5 als Ersatz für die ReaderWriterLock-Klasse eingeführt.

Mit der ReaderWriterLockSlim-Klasse können Sie eine nicht exklusive Sperre für eine gemeinsam genutzte Ressource erwerben, die häufige Lesevorgänge, aber seltene Aktualisierungen erfordert. Anstelle einer sich gegenseitig ausschließenden Sperre für eine gemeinsam genutzte Ressource, die häufige Lesevorgänge und seltene Aktualisierungen erfordert, können Sie diese Klasse verwenden, um eine Lesesperre für die gemeinsam genutzte Ressource und eine exklusive Schreibsperre für diese Ressource zu erhalten.

Deadlocks

Sie sollten die Verwendung einer Lock-Anweisung für den Typ vermeiden oder Anweisungen wie lock (this) verwenden, um die Synchronisierung in Ihrer Anwendung zu implementieren, da dies zu Deadlocks führen kann. Beachten Sie, dass Deadlocks auch auftreten können, wenn Sie die für eine gemeinsam genutzte Ressource erworbene Sperre für einen längeren Zeitraum halten. Sie sollten keine unveränderlichen Typen in Ihren Lock-Anweisungen verwenden. Sie sollten beispielsweise vermeiden, ein Zeichenfolgenobjekt als Schlüssel in Ihrer lock-Anweisung zu verwenden. Sie sollten die Verwendung der lock-Anweisung für einen öffentlichen Typ vermeiden. Es wird empfohlen, private oder geschützte Objekte zu sperren, die nicht interniert sind. Im Wesentlichen tritt eine Deadlock-Situation auf, wenn mehrere Threads aufeinander warten, um die Sperre für eine gemeinsam genutzte Ressource aufzuheben. In diesem MSDN-Artikel erfahren Sie mehr über Deadlocks.