Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   TInterlocked außerhalb eines TThreads? (https://www.delphipraxis.net/193187-tinterlocked-ausserhalb-eines-tthreads.html)

SneakyBagels 2. Jul 2017 08:42

TInterlocked außerhalb eines TThreads?
 
Ich säubere gerade meinen Code und ersetze an ein paar Stellen Inc() durch TInterlocked.Add().

Hat TInterlocked überall den Vorteil den der Befehl bringen sollte (Sicherheit beim Schreiben der Variable) oder muss das zwingend aus einem TThread aufgerufen werden?

Uwe Raabe 2. Jul 2017 09:08

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Zitat von SneakyBagels (Beitrag 1375784)
Hat TInterlocked überall den Vorteil den der Befehl bringen sollte (Sicherheit beim Schreiben der Variable) oder muss das zwingend aus einem TThread aufgerufen werden?

Was verstehst du denn unter "Sicherheit beim Schreiben"? Solange die zu inkrementierende Variable nur aus einem Thread (z.B. dem Hauptthread) benutzt wird, besteht ja überhaupt keine Gefahr. Sobald aber auch andere Threads ins Spiel kommen muss das Interlocked immer verwendet werden (auch aus dem Hauptthread) - solange keine anderen Schutzmechanismen (z.B. CriticalSection) aktiv sind.

SneakyBagels 2. Jul 2017 10:15

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Was verstehst du denn unter "Sicherheit beim Schreiben"? Solange die zu inkrementierende Variable nur aus einem Thread (z.B. dem Hauptthread) benutzt wird, besteht ja überhaupt keine Gefahr
Hat es denn irgendwelche Nachteile, wenn man trotzdem überall TInterLocked statt Inc verwendet?

Uwe Raabe 2. Jul 2017 10:54

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Zitat von SneakyBagels (Beitrag 1375787)
Hat es denn irgendwelche Nachteile, wenn man trotzdem überall TInterLocked statt Inc verwendet?

Durch den Oberhead beim Aufruf von TInterlocked ist das potentiell weniger performant:

Delphi-Quellcode:
Unit191.pas.39: TInterlocked.Increment(I);
005FA2E4 8D45F8           lea eax,[ebp-$08]
005FA2E7 BA01000000       mov edx,$00000001
005FA2EC E8B725F1FF      call TInterlocked.Add
005FA2F1 8945F4           mov [ebp-$0c],eax

Unit191.pas.40: Inc(I);
005FA2F4 FF45F8           inc dword ptr [ebp-$08]

SneakyBagels 2. Jul 2017 11:05

AW: TInterlocked außerhalb eines TThreads?
 
Ich habe das gerade mal geprüft und eine etwas längere Prozedur gestartet die gewisse Dinge tut - und das 3550 Mal.
Wenn es einen Unterschied gibt, dann liegt der im Millisekundenbereich.
Mit Inc() war der Prozess in 45 Sekunden erledigt, mit TInterLocked.Add in 44.

Und selbst wenn es umgekehrt wäre, würde ich trotzdem TInterLocked aktuell bevorzugen. Ich hatte noch sehr viele Stellen mit Inc und das in einer multithreaded Anwendung.

dummzeuch 2. Jul 2017 11:56

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Zitat von SneakyBagels (Beitrag 1375789)
Und selbst wenn es umgekehrt wäre, würde ich trotzdem TInterLocked aktuell bevorzugen. Ich hatte noch sehr viele Stellen mit Inc und das in einer multithreaded Anwendung.

Solange es sich um lokale Variablen handelt, ist Inc genauso "sicher" wie irgendein Interlocked-Befehl. Handelt es sich um globale oder sonstwie mehrfach benutzte Variablen (Felder), dann bedeutet die Verwendung von Interlocked-Befehlen nicht automatisch, dass die Verwendung threadsafe ist. Evtl. erzeugst Du damit lediglich ein falsches Gefühl der Sicherheit.

SneakyBagels 2. Jul 2017 12:01

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

dann bedeutet die Verwendung von Interlocked-Befehlen nicht automatisch, dass die Verwendung threadsafe ist. Evtl. erzeugst Du damit lediglich ein falsches Gefühl der Sicherheit.
Wofür soll TInterLocked denn sonst da sein?

Das würde ja das hier revidieren:
Zitat:

Sobald aber auch andere Threads ins Spiel kommen muss das Interlocked immer verwendet werden (auch aus dem Hauptthread) - solange keine anderen Schutzmechanismen (z.B. CriticalSection) aktiv sind.

Uwe Raabe 2. Jul 2017 13:30

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Zitat von SneakyBagels (Beitrag 1375797)
Zitat:

dann bedeutet die Verwendung von Interlocked-Befehlen nicht automatisch, dass die Verwendung threadsafe ist. Evtl. erzeugst Du damit lediglich ein falsches Gefühl der Sicherheit.
Wofür soll TInterLocked denn sonst da sein?

Das würde ja das hier revidieren:
Zitat:

Sobald aber auch andere Threads ins Spiel kommen muss das Interlocked immer verwendet werden (auch aus dem Hauptthread) - solange keine anderen Schutzmechanismen (z.B. CriticalSection) aktiv sind.

Das ist halt eine notwendige Bedingung, aber keine hinreichende.

jaenicke 2. Jul 2017 13:46

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Zitat von SneakyBagels (Beitrag 1375787)
Hat es denn irgendwelche Nachteile, wenn man trotzdem überall TInterLocked statt Inc verwendet?

Es ist abgesehen von der Performance auch der Codequalität nicht gerade zuträglich. Denn jeder Leser überlegt bei einem solchen Quelltext zuerst wie an solchen Stellen parallele Zugriffe erfolgen könnten.

Und umgekehrt denkt jemand vielleicht, dass der Code schon threadsicher ist, obwohl nur die eine Variable threadsicher verändert wird und der Rest nicht geschützt ist. Wenn ich so unseren Code durchschaue, sehe ich nicht viele Stellen, an denen solch ein Interlocked-Befehl ausreicht. Den verwenden wir zur Synchronisation mit anderen Threads, aber für sich genommen würde der nur an ein oder zwei Stellen etwas bringen.

Um eine saubere Trennung der Threads usw. kommst du mit so etwas nicht herum...

SneakyBagels 2. Jul 2017 13:52

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Um eine saubere Trennung der Threads usw. kommst du mit so etwas nicht herum...
Keine Sorge. Ich verwende nicht nur TInterLocked.

jaenicke 2. Jul 2017 16:30

AW: TInterlocked außerhalb eines TThreads?
 
Wenn die Threads sauber implementiert sind, macht es keinen Sinn ohne konkreten Anlass einfach irgendetwas zu locken.

SneakyBagels 2. Jul 2017 16:33

AW: TInterlocked außerhalb eines TThreads?
 
Meine Threads haben alle schreibenden Zugriff auf ein paar in einem Record befindlichen Variablen.

Ich könnte das auch ohne locken machen nur das wäre echt eine super dämliche Lösung:
jeder Thread schreibt in seine eigenen, lokalen Variablen und erst bein Thread-Destroy schreibe ich den lokalen Wert in ein globale Variable.
Somit hätte ich statt einem ständigem Locken nur noch einmal Locken.
Das ist aber böse, hässlich und einfach nicht gut also mache ich den Text mal grau :P

jaenicke 2. Jul 2017 18:39

AW: TInterlocked außerhalb eines TThreads?
 
Das heißt jeder Thread aktualisiert gegebenenfalls mehrere Variablen hintereinander?
Dann würde ich eine Klasse daraus machen und schlicht mit TMonitor.Enter...TMonitor.Exit arbeiten.

Oder geht es immer nur um einzelne Werte?
Dann würde ich ebenfalls eine Klasse daraus machen (geht aber auch als Record) und Setter benutzen (inline deklarieren wegen der Performance), die sich dann um die Locks kümmern.
Die können das dann auch mit TInterlocked machen, aber du musst das nicht überall im Quelltext machen.

Beides sind saubere Lösungen.

SneakyBagels 2. Jul 2017 19:01

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Dann würde ich ebenfalls eine Klasse daraus machen (geht aber auch als Record) und Setter benutzen (inline deklarieren wegen der Performance), die sich dann um die Locks kümmern.
Ist mir ehrlich gesagt zuviel Aufwand mit Klassen und dann den Settern.

Aktuell ist es schlicht so:
es existiert ein Record und auf die einzelnen Werte greife ich zu une modifiziere sie mit TInterLocked.
Und damit ich nur an einer Stelle im Code die Variable selber habe ist es so gelößt


Delphi-Quellcode:

// unit _globals.pas
type
 TTestRecord = packed record
  test, hallo, huhu: Int64;
 end;
var aTestRecord: TTestRecord;

// Unit _enums.pas
type
 TTestEnum = (test, hallo, huhu);

// unit log_utils.pas
procedure test(const aTestEnum: TTestEnum; iIncrement: Integer = 1);
begin
 case aTestEnum do
  TTestEnum.test:
   TInterLocked.Add(aTestRecord.test, iIncrement);

  TTestEnum.hallo:
   TInterLocked.Add(aTestRecord.hallo, iIncrement);

  TTestEnum.huhu:
   TInterLocked.Add(aTestRecord.huhu, iIncrement);
 end;
end;

// unit... überall da wo ich es brauche
test(TTestEnum.hallo, 5);
test(TTestEnum.huhu);
Wenn es auf einfache Art und Weise besser geht (wovon ich ausgehe) dann nehme ich diese Kritik gerne an!

jaenicke 2. Jul 2017 22:17

AW: TInterlocked außerhalb eines TThreads?
 
Sehr einfach:
Delphi-Quellcode:
// unit _globals.pas
type
  TTest = class
  private
    class var FTest, FHallo, FHuhu: Int64;
  public
    class procedure IncTest(const AValue: Int64 = 1); inline;
    class procedure IncHallo(const AValue: Int64 = 1); inline;
    class procedure IncHuhu(const AValue: Int64 = 1); inline;
    class property Test: Int64 read FTest;
    class property Hallo: Int64 read FHallo;
    class property Huhu: Int64 read FHuhu;
  end;

  { TTest }

class procedure TTest.IncHallo(const AValue: Int64 = 1);
begin
  TInterLocked.Add(FHallo, AValue);
end;

class procedure TTest.IncHuhu(const AValue: Int64 = 1);
begin
  TInterLocked.Add(FHuhu, AValue);
end;

class procedure TTest.IncTest(const AValue: Int64 = 1);
begin
  TInterLocked.Add(FTest, AValue);
end;

  // unit... überall da wo ich es brauche
procedure Test1;
begin
  TTest.IncHallo(5);
  TTest.IncHuhu;

// lesen:
  ShowMessage(IntToStr(TTest.Hallo));
end;
Deine Lösung empfinde ich als mehr Aufwand als diese (zusätzliche Variable, zusätzliches Set, zusätzliches case, ...). Und langsamer ist deine durch die zusätzliche Fallunterscheidung und das fehlende inline auch. Dazu kommt noch, dass die Funktionalität so auf drei Units verteilt ist.

Zusätzlicher Vorteil:
Von außen gibt es durch die Properties keinen schreibenden Zugriff mehr auf die Felder, trotzdem kann lesend direkt auf die Felder zugegriffen werden (da es keinen Getter gibt, werden direkt die Werte verwendet).

// EDIT:
Nebenbei, falls die Unitnamen echt sind:
Unterstriche in Unitnamen und Bezeichnern sind in Delphi unüblich. In Delphi ist CamelCase üblich und für Unitnamen auch Punkte. (Empfinde ich auch als deutlich besser lesbar.)
Bei uns würden die Units z.B. heißen (wenn es allgemeine Units für mehrere Projekte sind, daher Common):
Common.Utils.Logging.pas, Common.Types.Logging.pas, ... (Im Unterverzeichnis common\utils bzw. common\types)
Und so allgemeine Namen wie _globals oder so würde es bei uns nicht geben, da das nichts über die Funktion aussagt. Die Klasse hieße bei uns dann auch z.B. TLogging und wäre die einzige in der Unit.
Auf die Weise werden auch die Units nicht so groß und übersichtlich und man findet auch direkt die Units zu den Klassen.

SneakyBagels 2. Jul 2017 22:23

AW: TInterlocked außerhalb eines TThreads?
 
Ich gucke mir das mal ganz genau an um es auch zu verstehen.
Aktuell verwende ich solche Klassen-Konstrukte häufig. Record statt Class und statt inline verwende ich aber static (weil ich es damals so gefunden habe).

Aber ich gucke mal wie ich das bei mir umgesetzt bekomme.
Ich habe schon gelesen, dass inline wohl für kleine Prozeduren/Funktionen von Vorteil sein soll.
Wie sieht das denn mit Prozeduren/Funktionen aus, die 200+ Zeilen lang sind?

jaenicke 2. Jul 2017 23:19

AW: TInterlocked außerhalb eines TThreads?
 
Zitat:

Zitat von SneakyBagels (Beitrag 1375828)
statt inline verwende ich aber static (weil ich es damals so gefunden habe)

Das eine hat mit dem anderen nichts zu tun außer dass es beides der Performance dienlich ist. Mit inline sorgst du dafür, dass statt des Assemblerbefehls Call mit Parameterübergabe usw. der Quelltext der aufgerufenen Funktion direkt an der Stelle des Aufrufs eingebaut wird.
Mit static gibt es kleinste Optimierungen durch die Tatsache, dass EAX nicht als Self-Pointer benötigt wird. Das sind aber nur einzelne Assemblerbefehle, die wegfallen.

Bei größeren Funktionen bringt inline nicht mehr so viel, denn wenn eine Funktion z.B. nur 20 Assemblerbefehle enthält wie in diesem Fall bei IncTest usw. macht es schon einen Unterschied, ob noch 10 für den Methodenaufruf hinzukommen. Hat die Funktion 200 Zeilen Pascalcode (schon sehr viel übrigens, da schlägt jede Metrikanalyse für Quelltext Alarm), relativiert sich das, weil der Code für den Aufruf im Verhältnis zum Methodeninhalt sehr klein ist.

SneakyBagels 2. Jul 2017 23:41

AW: TInterlocked außerhalb eines TThreads?
 
Wieso gibt es hier eigentlich noch keinen "Danke"-Button?

Zu deinem Code oben.
Ich habe es jetzt erst einmal vorläufig so gemacht:
Delphi-Quellcode:
// unit _globals.pas (vorläufig bleibt es bis Zeit habe und richtig umbaue)
type
 TTestRecord = packed record
  test, hallo, huhu: Int64;
 end;
var aTestRecord: TTestRecord;

// Unit _enums.pas (das ist weg)
// type
// TTestEnum = (test, hallo, huhu);

// unit log_utils.pas
procedure test(iTarget: Int64; iIncrement: Integer = 1);
begin
 TInterLocked.Add(iTarget, iIncrement);
end;

// unit... überall da wo ich es brauche
test(aTestRecord.hallo, 5);
test(aTestRecord.huhu);
Ist das trotzdem schon besser als vorher?


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

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