Racebedingungen und Deadlocks

Visual Basic .NET oder Visual Basic bietet die Möglichkeit, Threads zum ersten Mal in Visual Basic Anwendungen zu verwenden. Threads führen zu Debugproblemen wie Racebedingungen und Deadlocks. In diesem Artikel werden diese beiden Probleme behandelt.

Originalversion des Produkts:   Visual Basic Visual Basic .NET
Ursprüngliche KB-Nummer:   317723

Wenn Race-Bedingungen 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 suchen nach dem Thread, der 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 für die Ausführung auf einem Prozessor zugewiesen. Wenn die für den Thread zugewiesene Zeit abläuft, wird der Kontext des Threads gespeichert, bis der Prozessor zum nächsten Mal aktiviert wird, und der Prozessor beginnt mit der Ausführung des nächsten Threads.

Wie kann ein einzeiligen Befehl eine Racebedingung verursachen?

Untersuchen Sie das folgende Beispiel, um zu sehen, wie eine Racebedingung auftritt. Es gibt zwei Threads, und beide aktualisieren eine freigegebene Variable namens "total " (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 davon 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 erhält die Möglichkeit zum Ausführen, führt aber nur die Schritte 1 bis 3 aus. Dies bedeutet, dass Thread 1 die Variable gelesen und die Hinzufügung abgeschlossen hat. Thread 1 wartet jetzt nur noch darauf, den neuen Wert 150 auszuschreiben. Nachdem Thread 1 beendet wurde, wird Thread 2 vollständig ausgeführt. Dies bedeutet, dass der berechnete Wert (85) in die Variable Totalgeschrieben wurde. Schließlich erhält Thread 1 wieder die Kontrolle und beendet die Ausführung. Er schreibt seinen Wert (150) aus. Wenn Thread 1 abgeschlossen ist, ist der Wert Total daher jetzt 150 statt 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 auf dem 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 thread 2 dann mit der Ausführung beginnen kann. Wenn diese Ereignisse auftreten, tritt das Problem nicht auf. Die Threadausführung ist nicht deterministisch, daher können Sie den Zeitpunkt oder die Reihenfolge der Ausführung nicht steuern. Beachten Sie außerdem, dass die Threads im Laufzeitmodus anders als im Debugmodus ausgeführt werden können. Außerdem können Sie sehen, dass der Fehler nicht auftritt, wenn Sie jeden Thread in Reihe ausführen. Diese Zufälligkeit erschwert das Nachverfolgen und Debuggen dieser Fehler erheblich.

Um zu verhindern, dass die Racebedingungen auftreten, können Sie freigegebene Variablen sperren, sodass immer nur ein Thread gleichzeitig Zugriff auf die freigegebene Variable hat. Führen Sie dies sparsam aus, denn wenn eine Variable in Thread 1 gesperrt ist und Thread 2 auch die Variable benötigt, wird die Ausführung von Thread 2 beendet, während Thread 2 wartet, bis Thread 1 die Variable freigibt. (Weitere Informationen finden Sie SyncLock im Abschnitt "Verweise " in diesem Artikel.)

Symptome für eine Racebedingung

Das häufigste Symptom einer Racebedingung sind unvorhersehbare Werte von Variablen, die zwischen mehreren Threads gemeinsam genutzt werden. Dies resultiert aus der Unvorhersehbarkeit der Reihenfolge, in der die Threads ausgeführt werden. Irgendwann gewinnt ein Thread, und irgendwann gewinnt der andere Thread. In anderen Fällen funktioniert die Ausführung ordnungsgemäß. Wenn jeder Thread separat ausgeführt wird, verhält sich der Variablenwert außerdem 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 darauf, dass der andere Thread die Variable freigibt. Da jeder Thread die Variable hält, die der andere Thread wünscht, geschieht nichts, und die Threads bleiben inaktiv.

Details und Beispiele für Deadlocks

Der folgende Code verfügt über zwei Objekte, LeftVal und 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 LeftValwerden darf. 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 der Thread 2 gesperrt ist, hält er an 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 es 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 keiner der Threads entsperrt die Variable, die er 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 der 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 des Codes angezeigt werden. Dies ist ein schwerer Fehler, der nachverfolgt werden kann, da aus demselben Code sowohl die richtige 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 Hängen bezeichnet. Mindestens zwei Threads warten auf eine Variable, die der andere Thread gesperrt hat. Die Threads fahren nicht fort, da keines der Threads seine Variable loslässt, bis die andere Variable aufgerufen wird. Das gesamte Programm kann hängen bleiben, 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, threads jedoch. 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 eigene Ausnahmehandler, Planungsprioritäten und eine Reihe von Strukturen, die vom Betriebssystem zum Speichern des Threadkontexts verwendet werden, wenn der Thread die Ausführung während der Zuweisung zum Prozessor nicht abschließen kann. Der Kontext wird bis zum nächsten Mal, wenn 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 Visual Studio Hilfe zu den folgenden Schlüsselwörtern:

  • 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 aus dem Missbrauch von SyncLock resultieren können. Beispielsweise kann dieser Befehl Racebedingungen verhindern, aber Deadlocks verursachen.

  • InterLocked. Ermöglicht eine Auswahl von threadsicheren Vorgängen für grundlegende numerische Variablen.

Weitere Informationen finden Sie unter Threads und Threading.