Threadsichere Komponenten
Aktualisiert: November 2007
Bei der Multithreadprogrammierung kommt es häufig vor, dass Ressourcen von mehreren Threads gemeinsam genutzt werden müssen. Beispielsweise, wenn mehrere Threads auf eine gemeinsam genutzte Datenbank zugreifen oder Aktualisierungen an einer Reihe von Systemvariablen vornehmen müssen. Wenn mehrere Threads gleichzeitig auf gemeinsam genutzte Ressourcen zugreifen, tritt u. U. eine Racebedingung ein. Eine Racebedingung liegt vor, wenn sich eine Ressource nach der Bearbeitung durch einen Thread in einem unzulässigen Zustand befindet und ein anderer Thread dann versucht, auf diese Ressource zuzugreifen und sie in diesem Zustand zu nutzen. Betrachten Sie das folgende Beispiel:
Public Class WidgetManipulator
Public TotalWidgets as Integer = 0
Public Sub AddWidget()
TotalWidgets += 1
Console.WriteLine("Total widgets = " & TotalWidgets.ToString)
End Sub
Public Sub RemoveWidgets()
TotalWidgets -= 10
End Sub
End Class
public class WidgetManipulator
{
public int TotalWidgets = 0;
public void AddWidget()
{
TotalWidgets++;
Console.WriteLine("Total widgets = " + TotalWidgets.ToString());
}
public void RemoveWidgets()
{
TotalWidgets -= 10;
}
}
Diese Klasse stellt zwei Methoden zur Verfügung. Durch die Methode AddWidget wird 1 zum TotalWidgets-Feld hinzuaddiert und der Wert in die Konsole geschrieben. Durch die zweite Methode wird 10 vom TotalWidgets-Wert subtrahiert. Was passiert nun, wenn zwei Threads gleichzeitig versuchen, auf dieselbe Instanz der WidgetManipulator-Klasse zuzugreifen? Ein Thread könnte AddWidget aufrufen, während der zweite Thread gleichzeitig RemoveWidgets aufruft. In diesem Fall könnte der Wert von TotalWidgets vom zweiten Thread geändert werden, bevor der erste Thread einen zulässigen Wert zurückgeben konnte. Diese Racebedingung kann die gemeldeten Ergebnisse verfälschen und fehlerhafte Daten verursachen.
Vermeiden von Racebedingungen durch die Verwendung von Sperren
Sie können kritische Codeabschnitte vor Racebedingungen schützen, indem Sie Sperren verwenden. Eine Sperre, die durch das Visual Basic-Schlüsselwort SyncLock-Anweisung oder das C#-Schlüsselwort lock-Anweisung dargestellt wird, gewährt einem einzelnen Ausführungsthread die exklusiven Ausführungsrechte für ein Objekt. Im folgenden Beispiel werden Sperren dargestellt:
SyncLock MyObject
' Insert code that affects MyObject.
End SyncLock
lock(MyObject)
{
// Insert code that affects MyObject.
}
Wenn eine Sperre erreicht wird, wird die Ausführung auf dem angegebenen Objekt (MyObject im vorliegenden Beispiel) blockiert, bis der Thread den exklusiven Zugriff auf das Objekt erhält. Die Sperre wird aufgehoben, sobald ihr Ende erreicht ist, und die Ausführung der Threads wird normal fortgesetzt. Es können nur Objekte gesperrt werden, die einen Verweis zurückgeben. Ein Werttyp kann nicht in dieser Weise gesperrt werden.
Nachteile von Sperren
Die Verwendung von Sperren garantiert zwar, dass mehrere Threads nicht gleichzeitig auf ein Objekt zugreifen, sie kann aber auch zu einem signifikanten Leistungsabfall führen. Stellen Sie sich ein Programm vor, in dem viele Threads ausgeführt werden. Wenn jeder Thread ein bestimmtes Objekt verwenden und vor der Ausführung auf eine exklusive Sperre für dieses Objekt warten muss, wird die Ausführung aller Threads unterbrochen, und die Threads stauen sich in einer Warteschlange. Dies führt zu einer Leistungsminderung. Aus diesem Grund sollten Sie Sperren nur dann verwenden, wenn Sie über Code verfügen, der als Einheit ausgeführt werden muss. Sie könnten z. B. mehrere Ressourcen aktualisieren, die von einander abhängig sind. Diese Art von Code wird als atomar bezeichnet. Wenn Sie die Verwendung von Sperren auf atomaren Code beschränken, können Sie Multithreadkomponenten schreiben, die die Sicherheit Ihrer Daten gewährleisten und dabei leistungsfähig bleiben.
Achten Sie auch darauf, Situationen zu vermeiden, in denen Deadlocks auftreten können. In diesem Fall warten mehrere Threads jeweils darauf, dass einer von ihnen gemeinsam genutzte Ressourcen freigibt. Beispiel: Für Thread 1 liegt eine Sperre für Ressource A vor und er wartet auf Ressource B. Für Thread 2 wiederum liegt eine Sperre für Ressource B vor und er wartet auf Ressource A. In diesem Fall kann keiner der beiden Threads fortgesetzt werden. Solche Deadlock-Situationen werden nur durch sorgfältiges Programmieren vermieden.
Siehe auch
Aufgaben
Gewusst wie: Koordinieren mehrerer Ausführungsthreads
Gewusst wie: Bearbeiten von Steuerelementen aus Threads
Exemplarische Vorgehensweise: Erstellen einer einfachen Multithreadkomponente mit Visual Basic
Exemplarische Vorgehensweise: Erstellen einer einfachen Multithreadkomponente mit Visual C#
Konzepte
Übersicht über ereignisbasierte asynchrone Muster