Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Threadsicherheit (https://www.delphipraxis.net/160703-threadsicherheit.html)

Desmulator 26. Mai 2011 21:22

Threadsicherheit
 
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. :roll:

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

himitsu 26. Mai 2011 21:55

AW: Threadsicherheit
 
Du kannst nicht "treadsicher" direkt auf diese Variable zugreifen.

Delphi-Quellcode:
procedure TInterfacedObject._Release();
var
  i: Integer;
begin
  i := InterlockedDecrement(refcount);
  if i = 0 then Destroy;
end;
Theoretisch würde jetzt dennoch knallen, wenn zur selben Zeit _AddRef und _Release aufgerufen wird, wenn das InterlockedIncrement kurz nach InterlockedDecrement aufgerufen würde, da das Objekt freigegeben, aber praktisch sollte sowas nie passieren.
Denn wurde die letzte Referenz freigegeben, kann diese Variable keine Referenz mehr abgeben und somit kann sowas nie passieren.
Solange keiner an der Referenzzählung rumspielt und nirgendwo eine "Referenz" existiert, welcher nicht in der Referenzzählung enthalten ist.

Uwe Raabe 27. Mai 2011 07:32

AW: Threadsicherheit
 
Zitat:

Zitat von himitsu (Beitrag 1103172)
Solange keiner an der Referenzzählung rumspielt und nirgendwo eine "Referenz" existiert, welcher nicht in der Referenzzählung enthalten ist.

In dem Fall hätte man auch in einer Single-Thread-Anwendung Probleme.


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:40 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz