Guten Abend,
in letzter Zeit befasse ich mich deutlich mehr mit Assembler und begebe mich, wie der Titel vielleicht schon verrät, in das Gebiet der Threads, sehr spannend, vorallem in Assembler.
Threads benutzen ist eine Sache, sie sicher zu benutzen eine andere. Während ich also arbeitet stellten sich mir einige Fragen, die eigentlich aus einem recht simplen Kontext entstanden.
Nehmen wir einfach mal das Beispiel für einen Referenzzähler, so wie TInterfacedObject es für uns macht. Ich kann mich vage erinnern, dass die Release-Methode ungefähr so aussah:
Delphi-Quellcode:
procedure TInterfacedObject._Release();
begin
InterlockedDecrement(refcount);
if refcount = 0 then Free();
end;
Sinngemäß sollte es das sein, analog dazu die AddRef-Methode:
Delphi-Quellcode:
procedure TInterfacedObject._AddRef();
begin
InterlockedIncrement(refcount);
end;
Wahrscheinlich hat sich schon einiges geändert oder stimmt so nicht, doch ich glaube das trifft das Ganze schon ziemlich genau. Nun Sinn der Nutzung von InterlockedIncrement resp. InterlockedDecrement ist in meinen Augen natürlich die Threadsicherheit. Zwischen dem Lesen, Manipulieren und Schreiben des Wertes kann kein andere Thread auf diesen zugreifen, bzw. anders gesagt, es wird als eine Operation angesehen ( atomic-operation ), in Assambler dem lock-Prefix entsprechend.
Nun stellt sich mir aber die Frage, ist das wirklich threadsicher? Folgendes Beispiel:
Code:
Thread1 ruft _Release() auf.
Der Zähler wird gesenkt:
InterlockedDecrement(refcount);
Der Wert wird geladen und verglichen, entsprechend folgt eine Sprungoperation oder nicht, schließlich der Call
if refcount = 0 then Free();
Kontextwechsel beim aufruf von Free. Ich meine mich zu erinnern irgendwo mal gelesen zu haben, dass hauptsächlich nach jumps, calls, rets o.Ä. ein solcher Hardwarekontextwechsel passiert.
Thread2 ruft _AddRef() auf.
Der Zähler wird erhöht.
InterlockedIncrement(refcount);
...
Erneuter Kontextwechsel.
Thread1 führt nun fleißig Free aus. Die Daten sind nicht mehr im Speicher, Thread2 ist sich aber sicher über seinen Besitz des Objektes.
Unaufhaltsam rast Thread2 nun in eine Zugriffsverletzung.
Nun, das ist für mich alles andere als sicher. Darum kam mir der Gedanke, benutzen wir doch einfach mal kritische Abschnitte. Packen wir außen rum und alles ist super. Naja ~
Die CriticalSection will auch initialisiert und finalisiert werden, wie fast alles andere am Computer. Folgendes Szenario: Unser erster Thread betritt nun diese kritische Sektion, für alle anderen ist es gesperrt, er ist nun in der Lage das Objekt und die damit verbundene kritische Sektion wieder zu finalisieren, was ja auch richtig ist, wenn das Objekt gelöscht wird, ist für die kritische Sektion kein bedarf mehr. Aber was ist mit unserem zweiten Thread, der nun vielleicht schon darauf wartet die kritische Sektion zu betreten ( ich rede jetzt von EnterCriticalSection z.b. ).
MSDN kommentiert so:
Zitat:
If a critical section is deleted while it is still owned, the state of the threads waiting for ownership of the deleted critical section is undefined.
Nun ein undefinierter Status ist mir sehr unsympatisch. Frage ist jetzt, was soll getan werden?
Ich kann natürlich so argumentieren, dass ich sage, es ist ein Designfehler meines Programms, wenn so etwas passieren kann, doch ich denke, das ganze lässt sich auch auf andere Themen übertragen.
Schönen Abend noch ;D