Einleitung
Immer wieder hört man, es sei absolut unmöglich selbst eine Alternative für Critical Sections zu erstellen, hiermit möchte ich belegen, das dies alles falsche Aussagen sind!
Was sind Critical Sections?
Die sog. Critical Sections dienen dazu, dass ein bestimmter Codeabschnitt nie gleichzeitig mit einem anderen bestimmten Codeabschnitt ausgeführt wird. Dies kann z.B. beim Bearbeiten von Strings nützlich sein (vor allem bei PureBasic), denn wenn dort mehrere Threads an einem String arbeiten führt das zum Abstürzen des Programms.
Wie kommt dieses Problem zustande?
Grund für dieses Problem ist vor allem der Speicherzugriff und die Gefahr, das wärend dem Ausführen des einen Threads der Prozessor auf einen anderen umschaltet. Anhand eines Beispieles könnte man es so erklären
MOV eax,[variable]
ADD eax,100
MOV [variable],eax
Angenommen unser Prozessor switcht nun nach dem 1. Befehl (MOV eax, ...) in einen anderen Thread der den gleichen Code beinhaltet und der zur Variable 100 addiert! Zurück beim anderen Thread hat sich EAX jedoch nicht geändert, deshalb wird hier in diesem Beispiel einmal zählen unterschlagen!
Hierbei kann das Programm zwar nicht abstürzen, aber die Ursache ist eigentlich die gleiche!
Ideen zur Lösung des Problems
Allgemein kann man nun sagen, das etwas Threadsicher ist, wenn nur EIN einziger Speicher-Zugriffs-Befehl für eine Variable verwendet wurde (oder es sich natürlich um verschiedene Speicherbereiche handelt)
Um trotzdem Strings/LinkedLists/Speicherbereiche/Globale Variablen verwenden zu können, die standartmäßig unsicher sind, hier einen Code für eine Critical Section, die (fast) ohne fremde APIs auskommt: der einzige Befehl der verwendet wird dient dazu, die CPU-Auslastung zu erniedrigen xD
Die hier beschriebene Lösung verwendet dafür den etwas weniger bekannten, jedoch seit dem 486er vorhandenen Befehl XADD
[XADD Ziel, Quelle], das für "exchange and add" steht führt nacheinander zwei Aktionen durch:
- Vertauscht den Inhalt der beiden Operatoren
- Addiert beide Operatoren und schreibt das Ergebnis in den ersten Operator zurück
Dadurch können wir den Variableninhalt auslesen und gleichzeitig manipulieren, ohne dabei den Thread unsicher werden zu lassen!
EDIT: Den folgenden Code am besten mit Debugger starten, da keine grafische Oberfläche vorhanden ist! Zum testen kann man z.B. einfach mal die Enters und Leavs auskommentieren, und man wird merken, das das Programm schon nach kurzer Zeit crasht!
Code: Alles auswählen
;-
;- PureFan's Critical Sections *SPECIAL*
;-
;- Posted in: German PureBasic Board
;- Date: 20.04.05
;-
Structure Pure_CS
In.l
Out.l
EndStructure
Procedure Pure_InitCS(*PCS.Pure_CS)
*PCS\In=0
*PCS\Out=0
EndProcedure
Procedure Pure_EnterCS(*PCS.Pure_CS)
!POP Edi ;POP/PUSH dient zum schnellen Einlesen des Parameters *PCS in das Register EDI
!PUSH Edi
!XOR Esi,Esi
!INC Esi
!XADD [Edi],Esi ;XOR/INC/XADD liest zunächst den alten IN-Status in die Variable ESI aus
;und erhöht diese danach um 1
!.loop:
!PUSH dword 1
!CALL _Sleep@4 ;1msec warten
!CMP [Edi+4],Esi ;Wartet bis der OUT-Status dem gespeicherten Wert ESI entspricht
!JNE .loop
EndProcedure
Procedure Pure_LeaveCS(*PCS.Pure_CS)
!POP Edi
!PUSH Edi
!INC dword[Edi+4] ;Erhöht den OUT-Status um 1
EndProcedure
;/
;/ Example for PureFan's Critical Sections
;/
Global SichereStrings.Pure_CS
Pure_InitCS(SichereStrings)
Global GlobalerString.s
Procedure String_Thread(Parameter)
Repeat
Pure_EnterCS(SichereStrings)
lokaler_string.s=GlobalerString+"hallo"
GlobalerString.s=Right(lokaler_string.s,100)
Pure_LeaveCS(SichereStrings)
ForEver
EndProcedure
For I=1 To 10
CreateThread(@String_Thread(),I)
Next I
Repeat
Pure_EnterCS(SichereStrings)
lokaler_string.s=GlobalerString+"hallo"
GlobalerString.s=Right(lokaler_string.s,100)
Pure_LeaveCS(SichereStrings)
ForEver