Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Multithreading erfordert eine sorgfältige Programmierung. Bei den meisten Aufgaben können Sie die Komplexität reduzieren, indem Sie Anforderungen für die Ausführung durch Threadpool-Threads in eine Warteschlange einreihen. In diesem Thema werden schwierigere Situationen behandelt, z. B. die Koordination der Arbeit mehrerer Threads oder das Behandeln von Threads, die blockiert werden.
Hinweis
Ab .NET Framework 4 stellen die Task Parallel Library (TPL) und PLINQ APIs bereit, die einige der Komplexität und der Risiken der Multithreadprogrammierung reduzieren. Weitere Informationen finden Sie unter Parallel Programming in .NET.
Deadlocks und Racebedingungen
Multithreading löst Probleme mit Durchsatz und Reaktionsfähigkeit, aber dadurch werden neue Probleme eingeführt: Deadlocks und Rennbedingungen.
Stockung
Ein Deadlock tritt auf, wenn jeder von zwei Threads versucht, eine Ressource zu sperren, die die andere bereits gesperrt hat. Weder Thread kann weitere Fortschritte machen.
Viele Methoden der Klassen des verwalteten Threadings stellen Timeouts bereit, mit deren Hilfe Sie Deadlocks entdecken können. Der folgende Code versucht beispielsweise, eine Sperre für ein Objekt mit dem Namen lockObjectabzurufen. Wenn die Sperrung nicht innerhalb von 300 Millisekunden erfolgt, gibt Monitor.TryEnter den Wert false zurück.
If Monitor.TryEnter(lockObject, 300) Then
Try
' Place code protected by the Monitor here.
Finally
Monitor.Exit(lockObject)
End Try
Else
' Code to execute if the attempt times out.
End If
if (Monitor.TryEnter(lockObject, 300)) {
try {
// Place code protected by the Monitor here.
}
finally {
Monitor.Exit(lockObject);
}
}
else {
// Code to execute if the attempt times out.
}
Rennbedingungen
Eine Racebedingung ist ein Fehler, der auftritt, wenn das Ergebnis eines Programms davon abhängt, welche von zwei oder mehr Threads zuerst einen bestimmten Codeblock erreicht. Das Ausführen des Programms erzeugt viele Male unterschiedliche Ergebnisse, und das Ergebnis einer gegebenen Ausführung kann nicht vorhergesagt werden.
Ein einfaches Beispiel einer Racebedingung ist die Erhöhung eines Feldes. Angenommen, eine Klasse verfügt über ein privates statisches Feld (Freigegeben in Visual Basic), das jedes Mal erhöht wird, wenn eine Instanz der Klasse erstellt wird, indem Code wie objCt++; (C#) oder objCt += 1 (Visual Basic) verwendet wird. Dieser Vorgang erfordert das Laden des Werts aus objCt einem Register, das Erhöhen des Werts und das Speichern in objCt.
In einer Multithread-Anwendung kann ein Thread, der den Wert geladen und erhöht hat, von einem anderen Thread unterbrochen werden, der alle drei Schritte ausführt. Wenn der erste Thread die Ausführung fortsetzt und seinen Wert speichert, überschreibt er objCt, ohne die Tatsache zu berücksichtigen, dass sich der Wert in der Zwischenzeit geändert hat.
Diese spezielle Racebedingung lässt sich mit den Methoden der Interlocked-Klasse (z. B. Interlocked.Increment) problemlos vermeiden. Informationen zu anderen Techniken zum Synchronisieren von Daten zwischen mehreren Threads finden Sie unter "Synchronisieren von Daten für Multithreading".
Rennbedingungen können auch auftreten, wenn Sie die Aktivitäten mehrerer Threads synchronisieren. Wenn Sie eine Codezeile schreiben, müssen Sie überlegen, was passieren könnte, wenn ein Thread die Zeile nicht ausführt (oder bevor eine der einzelnen Maschinenanweisungen der Zeile ausgeführt wird) und ein anderer Thread ihn überholt.
Statische Mitglieder und statische Konstruktoren
Eine Klasse wird erst initialisiert, wenn der Klassenkonstruktor (static Konstruktor in C#, Shared Sub New in Visual Basic) die Ausführung abgeschlossen hat. Um die Ausführung von Code für einen nicht initialisierten Typ zu verhindern, blockiert die Common Language Runtime alle Aufrufe von anderen Threads an static Member der Klasse (Shared Member in Visual Basic), bis der Klassenkonstruktor ausgeführt wurde.
Wenn beispielsweise ein Klassenkonstruktor einen neuen Thread startet und die Threadprozedur ein static Element der Klasse aufruft, blockiert der neue Thread, bis der Klassenkonstruktor abgeschlossen ist.
Dies gilt für jeden Typ, der über einen static Konstruktor verfügen kann.
Anzahl der Prozessoren
Ob mehrere Prozessoren vorhanden sind oder nur ein Prozessor auf einem System verfügbar ist, kann die Multithreadarchitektur beeinflussen. Weitere Informationen finden Sie unter "Anzahl der Prozessoren".
Verwenden Sie die Environment.ProcessorCount Eigenschaft, um die Anzahl der zur Laufzeit verfügbaren Prozessoren zu ermitteln.
Allgemeine Empfehlungen
Beachten Sie bei der Verwendung mehrerer Threads die folgenden Richtlinien:
Verwenden Thread.Abort Sie diese Option nicht, um andere Threads zu beenden. Das Aufrufen
Aborteines anderen Threads ähnelt dem Auslösen einer Ausnahme für diesen Thread, ohne zu wissen, welcher Punkt in der Verarbeitung des Threads erreicht wurde.Verwenden Sie Thread.Suspend und Thread.Resume nicht, um die Aktivitäten mehrerer Threads zu synchronisieren. Verwenden Sie Mutex, ManualResetEvent, , AutoResetEventund Monitor.
Steuern Sie nicht die Ausführung von Arbeitsthreads aus Ihrem Hauptprogramm (z. B. mithilfe von Ereignissen). Entwerfen Sie stattdessen Ihr Programm so, dass Arbeitsthreads dafür verantwortlich sind, zu warten bis die Arbeit verfügbar ist, sie auszuführen und andere Teile Ihres Programms zu benachrichtigen, wenn sie fertig sind. Bei nicht blockierenden Arbeitsthreads sollten Sie u. U. Threadpoolthreads verwenden. Monitor.PulseAll ist in Situationen nützlich, in denen Arbeitsthreads blockieren.
Verwenden Sie Typen nicht als Sperrobjekte. Das heißt, vermeiden Sie Code wie
lock(typeof(X))in C# oderSyncLock(GetType(X))in Visual Basic oder die Verwendung von Monitor.EnterType Objekten. Für einen entsprechenden Typ gibt es nur eine Instanz von System.Type pro Anwendungsdomäne. Wenn der von Ihnen gesperrte Typ öffentlich ist, kann er von fremdem Code gesperrt werden, was zu Deadlocks führt. Weitere Probleme finden Sie unter Bewährte Methoden für Zuverlässigkeit.Seien Sie beim Sperren von Instanzen vorsichtig, zum Beispiel
lock(this)in C# oderSyncLock(Me)in Visual Basic. Wenn ein anderer Code in Ihrer Anwendung, außerhalb des Typs, eine Sperre für das Objekt akzeptiert, können Deadlocks auftreten.Stellen Sie sicher, dass ein Thread, der einen Monitor betreten hat, diesen Monitor immer verlässt, auch wenn eine Ausnahme auftritt, während sich der Thread im Monitor befindet. Die C# lock-Anweisung und die Visual Basic SyncLock-Anweisung stellen dieses Verhalten automatisch bereit, indem ein finally-Block verwendet wird, um sicherzustellen, dass Monitor.Exit aufgerufen wird. Wenn Sie nicht sicherstellen können, dass Exit aufgerufen wird, sollten Sie das Design so ändern, dass Mutex verwendet wird. Ein Mutex wird automatisch freigegeben, wenn der Thread, der zurzeit den Mutex besitzt, beendet wird.
Verwenden Sie mehrere Threads für Vorgänge, die unterschiedliche Ressourcen erfordern, und vermeiden Sie das Zuweisen mehrerer Threads zu einer einzelnen Ressource. Beispielsweise profitiert jede Aufgabe, die E/A umfasst, von einem eigenen Thread, da dieser Thread während der E/A-Vorgänge blockiert und somit anderen Threads die Ausführung ermöglicht. Die Benutzereingabe ist eine weitere Ressource, die von einem dedizierten Thread profitiert. Auf einem Computer mit einem einzelnen Prozessor gibt es eine Aufgabe, die intensive Berechnungen erfordert und mit Benutzereingaben sowie Aufgaben, die Ein-/Ausgabe (E/A) umfassen, koexistiert, aber mehrere rechenintensive Aufgaben konkurrieren miteinander.
Erwägen Sie die Verwendung von Methoden der Interlocked Klasse für einfache Zustandsänderungen, anstatt die
lockAnweisung (SyncLockin Visual Basic) zu verwenden. DielockAnweisung ist ein gutes allgemeines Tool, aber die Interlocked Klasse bietet eine bessere Leistung für Updates, die atomar sein müssen. Sie führt intern ein einzelnes lock-Präfix aus, wenn kein Konflikt vorliegt. Achten Sie in Codeüberprüfungen auf Code wie in den folgenden Beispielen. Im ersten Beispiel wird eine Zustandsvariable erhöht:SyncLock lockObject myField += 1 End SyncLocklock(lockObject) { myField++; }Sie können die Leistung verbessern, indem Sie die Increment Methode anstelle der
lockAnweisung wie folgt verwenden:System.Threading.Interlocked.Increment(myField)System.Threading.Interlocked.Increment(myField);Hinweis
Verwenden Sie die Add Methode für Atomschritte, die größer als 1 sind.
Im zweiten Beispiel wird eine Verweistypvariable nur aktualisiert, wenn es sich um einen Nullverweis handelt (
Nothingin Visual Basic).If x Is Nothing Then SyncLock lockObject If x Is Nothing Then x = y End If End SyncLock End Ifif (x == null) { lock (lockObject) { x ??= y; } }Die Leistung kann stattdessen mithilfe der CompareExchange Methode wie folgt verbessert werden:
System.Threading.Interlocked.CompareExchange(x, y, Nothing)System.Threading.Interlocked.CompareExchange(ref x, y, null);Hinweis
Die Überladung der CompareExchange<T>(T, T, T)-Methode stellt eine typsichere Alternative für Verweistypen dar.
Empfehlungen für Klassenbibliotheken
Beachten Sie beim Entwerfen von Klassenbibliotheken für Multithreading die folgenden Richtlinien:
Vermeiden Sie nach Möglichkeit die Notwendigkeit der Synchronisierung. Dies gilt insbesondere für stark verwendete Code. Beispielsweise kann ein Algorithmus angepasst werden, um eine Racebedingung zu tolerieren und nicht zu unterbinden. Durch unnötige Synchronisierung wird die Leistung verringert, und es entsteht die Gefahr von Deadlocks und Racebedingungen.
Machen Sie statische Daten (
Sharedin Visual Basic) standardmäßig threadsicher.Legen Sie Instanzdaten nicht als standardmäßig threadsicher fest. Das Hinzufügen von Sperren zum Erstellen von threadsicherem Code verringert die Leistung, erhöht die Sperrkonkurrenz und erhöht die Gefahr von Deadlocks. In gängigen Anwendungsmodellen führt jeweils nur ein Thread Benutzercode aus, wodurch die Notwendigkeit der Threadsicherheit minimiert wird. Aus diesem Grund sind die .NET-Klassenbibliotheken standardmäßig nicht threadsicher.
Vermeiden Sie die Bereitstellung statischer Methoden, die den statischen Zustand ändern. In gängigen Serverszenarien wird der statische Zustand für Anforderungen freigegeben, was bedeutet, dass mehrere Threads diesen Code gleichzeitig ausführen können. Dies eröffnet die Möglichkeit von Threadingfehlern. Erwägen Sie die Verwendung eines Entwurfsmusters, das Daten in Instanzen kapselt, die nicht über Anfragen hinweg geteilt werden. Wenn statische Daten synchronisiert werden, können Aufrufe zwischen statischen Methoden, die den Zustand ändern, zu Deadlocks oder redundanter Synchronisierung führen und die Leistung beeinträchtigen.
Siehe auch
- Einfädelnd
- Threads and Threading (Threads und Threading)