Freigeben über


Rennbedingungen und Deadlocks

Visual Basic .NET oder Visual Basic bietet die Möglichkeit, Threads in Visual Basic-Anwendungen zum ersten Mal zu verwenden. Threads führen Debuggingprobleme wie Rennbedingungen und Deadlocks ein. In diesem Artikel werden diese beiden Probleme erläutert.

Originalproduktversion: Visual Basic, Visual Basic .NET
Ursprüngliche KB-Nummer: 317723

Wenn Rennbedingungen auftreten

Eine Racebedingung tritt auf, wenn zwei Threads gleichzeitig auf eine freigegebene Variable zugreifen. Der erste Thread liest die Variable, und der zweite Thread liest denselben Wert aus der Variablen. Anschließend führen der erste Thread und der zweite Thread ihre Vorgänge für den Wert aus, und sie fahren weiter, um zu sehen, welcher Thread den Wert zuletzt in die freigegebene Variable schreiben kann. Der Wert des Threads, der seinen Wert zuletzt schreibt, wird beibehalten, da der Thread über den Wert schreibt, den der vorherige Thread geschrieben hat.

Details und Beispiele für eine Rennbedingung

Jedem Thread wird ein vordefinierter Zeitraum zugewiesen, der auf einem Prozessor ausgeführt werden soll. Wenn die Zeit, die für den Thread zugewiesen ist, abläuft, wird der Kontext des Threads gespeichert, bis die nächste Aktivierung des Prozessors erfolgt, und der Prozessor beginnt mit der Ausführung des nächsten Threads.

Wie kann ein Einzeilbefehl eine Racebedingung verursachen

Sehen Sie sich das folgende Beispiel an, um zu sehen, wie eine Rennbedingung auftritt. Es gibt zwei Threads, und beide aktualisieren eine freigegebene Variable, die als Summe bezeichnet wird (die wie dword ptr ds:[031B49DCh] im Assemblycode dargestellt wird).

  • Thread 1

    Total = Total + val1
    
  • Thread 2

    Total = Total - val2
    

Assemblycode (mit Zeilennummern) aus der Kompilierung des vorherigen Visual Basic-Codes:

  • Thread 1

    1. mov eax,dword ptr ds:[031B49DCh]
    2. add eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 7611097F
    6. mov dword ptr ds:[031B49DCh],eax
    
  • Thread 2

    1. mov eax,dword ptr ds:[031B49DCh]
    2. sub eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 76110BE7
    6. mov dword ptr ds:[031B49DCh],eax
    

Wenn Sie sich den Assemblycode ansehen, können Sie sehen, wie viele Vorgänge der Prozessor auf der unteren Ebene ausführt, um eine einfache Additionsberechnung auszuführen. Ein Thread kann während seiner Zeit auf dem Prozessor den gesamten Assemblycode oder einen Teil seines Assemblycodes ausführen. Sehen Sie sich nun an, wie eine Racebedingung aus diesem Code auftritt.

Total ist 100, val1 ist 50 und val2 ist 15. Thread 1 kann ausgeführt werden, führt jedoch nur die Schritte 1 bis 3 aus. Dies bedeutet, dass Thread 1 die Variable liest und die Ergänzung abgeschlossen hat. Thread 1 wartet nun nur darauf, den neuen Wert 150 zu schreiben. Nachdem Thread 1 beendet wurde, wird Thread 2 vollständig ausgeführt. Dies bedeutet, dass der Wert, den er berechnet hat (85) in die Variable Totalgeschrieben hat. Schließlich erhält Thread 1 wieder Kontrolle und beendet die Ausführung. Er schreibt seinen Wert aus (150). Daher ist der Wert von Thread 1 jetzt 150 anstelle von Total 85.

Sie können sehen, wie dies ein großes Problem sein könnte. Wenn es sich um ein Bankprogramm handelt, hätte der Kunde geld in ihrem Konto, das nicht vorhanden sein sollte.

Dieser Fehler ist zufällig, da Thread 1 die Ausführung abschließen kann, bevor der Prozessor abläuft, und dann kann Thread 2 mit der Ausführung beginnen. Wenn diese Ereignisse auftreten, tritt das Problem nicht auf. Die Threadausführung ist nicht deterministisch, daher können Sie die Zeit oder Reihenfolge der Ausführung nicht steuern. Beachten Sie außerdem, dass die Threads in laufzeit- und debugmodus unterschiedlich ausgeführt werden können. Außerdem können Sie sehen, dass der Fehler nicht auftritt, wenn Sie jeden Thread in Serie ausführen. Diese Zufallszahl erschwert das Auffinden und Debuggen dieser Fehler.

Um zu verhindern, dass die Racebedingungen auftreten, können Sie freigegebene Variablen sperren, sodass nur ein Thread gleichzeitig Zugriff auf die freigegebene Variable hat. Führen Sie dies sparsam aus, da bei gesperrter Variable in Thread 1 und Thread 2 auch die Variable benötigt wird, die Ausführung von Thread 2 beendet wird, während Thread 2 auf die Freigabe der Variablen wartet. (Weitere Informationen finden Sie SyncLock im Abschnitt "Verweise " dieses Artikels.)

Symptome für einen Rennzustand

Das häufigste Symptom einer Racebedingung ist unvorhersehbare Werte von Variablen, die zwischen mehreren Threads geteilt werden. Dies ergibt sich aus der Unvorstellbarkeit der Reihenfolge, in der die Threads ausgeführt werden. Irgendwann gewinnt ein Thread, und irgendwann gewinnt der andere Thread. In anderen Zeiten funktioniert die Ausführung ordnungsgemäß. Wenn jeder Thread separat ausgeführt wird, verhält sich der Variablewert ebenfalls ordnungsgemäß.

Wenn Deadlocks auftreten

Ein Deadlock tritt auf, wenn zwei Threads jeweils eine andere Variable gleichzeitig sperren und dann versuchen, die Variable zu sperren, die der andere Thread bereits gesperrt hat. Daher beendet jeder Thread die Ausführung und wartet, bis der andere Thread die Variable freigibt. Da jeder Thread die Variable hält, die der andere Thread möchte, tritt nichts auf, und die Threads bleiben inaktiv.

Details und Beispiele für Deadlocks

Der folgende Code verfügt über zwei Objekte und LeftVal RightVal:

  • Thread 1

    SyncLock LeftVal
        SyncLock RightVal
            'Perform operations on LeftVal and RightVal that require read and write.
        End SyncLock
    End SyncLock
    
  • Thread 2

    SyncLock RightVal
        SyncLock LeftVal
            'Perform operations on RightVal and LeftVal that require read and write.
        End SyncLock
    End SyncLock
    

Ein Deadlock tritt auf, wenn Thread 1 gesperrt werden darf LeftVal. Der Prozessor beendet die Ausführung von Thread 1 und beginnt mit der Ausführung von Thread 2. Thread 2 sperrt RightVal und versucht dann zu sperren LeftVal. Da LeftVal sie gesperrt ist, stoppt Thread 2 und wartet auf LeftVal die Veröffentlichung. Da Thread 2 beendet wird, darf Thread 1 weiterhin ausgeführt werden. Thread 1 versucht zu sperren RightVal , kann aber nicht, da Thread 2 sie gesperrt hat. Daher beginnt Thread 1, zu warten, bis RightVal verfügbar ist. Jeder Thread wartet auf den anderen Thread, da jeder Thread die Variable gesperrt hat, auf die der andere Thread wartet, und kein Thread entsperrt die Variable, die sie hält.

Ein Deadlock tritt nicht immer auf. Wenn Thread 1 beide Sperren ausführt, bevor der Prozessor sie beendet, kann Thread 1 seine Vorgänge ausführen und dann die freigegebene Variable entsperren. Nachdem Thread 1 die Variable entsperrt hat, kann Thread 2 wie erwartet mit seiner Ausführung fortfahren.

Dieser Fehler scheint offensichtlich, wenn diese Codeausschnitte nebeneinander platziert werden, aber in der Praxis kann der Code in separaten Modulen oder Bereichen Ihres Codes angezeigt werden. Dies ist ein schwerer Fehler, um nachzuverfolgen, da aus diesem Code sowohl die korrekte Ausführung als auch die falsche Ausführung auftreten können.

Symptome für Deadlocks

Ein häufiges Symptom von Deadlock ist, dass das Programm oder die Gruppe von Threads nicht mehr reagiert. Dies wird auch als Hang bezeichnet. Mindestens zwei Threads warten auf eine Variable, die der andere Thread gesperrt hat. Die Threads werden nicht fortgesetzt, da beide Threads ihre Variable erst freigeben, wenn sie die andere Variable abruft. Das gesamte Programm kann hängen, wenn das Programm auf einen oder beide dieser Threads wartet, um die Ausführung abzuschließen.

Was ist ein Thread?

Prozesse werden verwendet, um die verschiedenen Anwendungen zu trennen, die zu einem bestimmten Zeitpunkt auf einem einzelnen Computer ausgeführt werden. Das Betriebssystem führt keine Prozesse aus, aber Threads führen dies aus. Ein Thread ist eine Ausführungseinheit. Das Betriebssystem weist einem Thread Prozessorzeit für die Ausführung der Aufgaben des Threads zu. Ein einzelner Prozess kann mehrere Ausführungsthreads enthalten. Jeder Thread verwaltet seine eigenen Ausnahmehandler, Planungsprioritäten und eine Reihe von Strukturen, die vom Betriebssystem zum Speichern des Threadkontexts verwendet werden, wenn der Thread während der Zeit, die er dem Prozessor zugewiesen wurde, nicht abschließen kann. Der Kontext wird bis zum nächsten Zeitpunkt, zu dem der Thread Prozessorzeit empfängt, gehalten. Der Kontext enthält alle Informationen, die der Thread benötigt, um die Ausführung nahtlos fortzusetzen. Diese Informationen umfassen die Prozessorregister des Threads und den Aufrufstapel innerhalb des Adressraums des Hostprozesses.

References

Weitere Informationen finden Sie in der Visual Studio-Hilfe für die folgenden Schlüsselwörter:

  • SyncLock. Ermöglicht das Sperren eines Objekts. Wenn ein anderer Thread versucht, dasselbe Objekt zu sperren, wird es blockiert, bis der erste Thread loslässt. Verwenden Sie SyncLock sorgfältig, da Probleme durch den Missbrauch von SyncLock entstehen können. Beispielsweise kann dieser Befehl Rennbedingungen verhindern, aber Deadlocks verursachen.

  • InterLocked. Ermöglicht einen ausgewählten Satz threadsicherer Vorgänge für grundlegende numerische Variablen.

Weitere Informationen finden Sie unter Threads und Threading.