![]() |
Delphi-Version: XE7
TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Hallo zusammen,
ich habe zwei Threads die beide auf gemeinsame Felder zugreifen. Der eine Thread kann jeweils nur schreiben, der andere nur lesen. Muss ich nun alle schreibenden Zugriffe z.b. per TInterlocked.Exchange absichern oder ist das bei nur lesendem Zugriff des einen Threads nicht nötig? Kann es überhaupt passieren, dass die CPU Datentypen wie Byte, Word, DWord nicht atomar schreibt? Viele Grüße Zacherl |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Diese Interlocked-Funktionen arbeiten "atomar", so daß beim Schreibzugriff kein anderer Lesen kann.
Falls die CPU es nicht absichert, daß ein anderer Thread gleichzeitig liest, während ein Anderer schreibt, dan könnte es vielleicht passieren, daß zu von den 4 Bytes eines DWORD/INT teilweises noch ein paar Bytes von dem alten wert mit ausliest. :gruebel: |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Der Speicher wird immer in Busbreite ausgelesen/geschreiben. Einen zusammengestückelten Wert wie 2 Bytes vom alten und 2 Bytes vom neuen Wert gibt es nicht.
Wenn du also absolut schreibst (kein addieren) dann kannst du den Wert einfach dort hineinschreiben. Sobald du mehr als einen Zugriff machen musst, um das gewünschte Resultat zu erreichen, musst du den Bereich schützen. |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Zitat:
Bei zwei Threads die beide schreiben, muss ich doch theoretisch sogar auch nichts absichern, sofern der neue Wert nicht vom alten Wert abhängt oder? |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Schreibe einen weiteren, der aus dieser Variable liest. Starte jetzt mehrere von beiden Threads und schau, ob da etwas knallt. Dann hat man auch einen Erfahrungswert und wird noch viel ruhiger :) |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
In Richtung Multiplatform betrachtet ... benutzte einfach die neuen Interlocked-Funktionen aus der System-Unit.
Die aus der Windows-Unit kann man bekanntlich nicht überall benutzen. Aus irgendeinem unerfindlichem Grunde mach der Mac die WinAPIs nicht. :stupid: |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Delphi-Quellcode:
steckt.
packed record
Außerdem willst du eines Tages vielleicht doch mit mehreren Threads den Wert beschreiben und freust dich dann dass es direkt ohne Anlauf möglich ist. Ich würde einfach
Delphi-Quellcode:
oder sonst was verwenden und glücklich sein.
TInterLocked
|
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
|
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Zu den packed records. Sowas würde also nicht funktionieren? Verstehe ich jetzt nicht ganz .. :shock:
Delphi-Quellcode:
Außer des Alignments (was beim ersten Element hier in dem Beispiel natürlich egal wäre), ändert das "packed" Attribut doch eigentlich nichts an der ganzen Geschichte.
type
TRec = packed record A: Integer; end; .. Rec.A := 100; |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Delphi-Quellcode:
führt dazu, daß A eher nicht auf einer der CPU genehmen Speichergrenze liegt und die damit zwei Zyklen zum Schreiben braucht. Zwischen diesen beiden Zyklen kann aber ein Lesezugriff (ebenfalls zwei Zyklen) erfolgen. Das hängt aber auch konkret von der verwendeten CPU ab.
type
TRec = packed record B: Byte; A: Integer; end; |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
|
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
|
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Das ist eine Frage der Ausrichtung...
Wenn Du mit {$A1} arbeitest ist es nicht sicher... Wie ich gelesen haben übrigens auch nicht zu 100% bei einem Interger. Also lieber ein Interlock oder CS Mavarik |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Es ist in 99% der Fälle besser die entsprechenden Bibliotheksfunktionen und Datentypen (richtig ausgerichtet) für atomare Zugriffe zu benutzen. Und es nicht nur der Speicher selbst, auch der Compiler und Out-Of-Order-Execution kann dir reinpfuschen:
Delphi-Quellcode:
// Thread 1
ptr = Class.create(): ptr.value = 1337; global_ptr = ptr;
Delphi-Quellcode:
Der Compiler weiß nicht, dass global_ptr von mehreren Threads benutzt wird und ordnet das wegen Optimierung so um (oder der Prozessor macht das; warum auch nicht):
// Thread 2
write(global_ptr.foo); // 1337?
Delphi-Quellcode:
Den Fehler finde erst mal :mrgreen:
// Thread 1
ptr = Class.create(): global_ptr = ptr; ptr.value = 1337; Die Bibliotheksfunktionen sind entweder dem Compiler bekannt oder zumindest undurchsichtig, so dass er solche Spielchen lässt. Intern wird da sichergestellt, dass der Prozessor nichts umordnet (Memory Barriers oder andere Tricks). Wenn es nur um Sachen geht, die schiefgehen können (z.B. Integer für Fortschrittsanzeige auslesen) mag das alles gut gehen. Alles darüber hinaus sollte man vorsichtig angehen. |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Verdammt, dann habe ich wohl etwas Arbeit vor mir :?
Wie gehe ich am besten bei Enumtypen vor? Alles mit $Z4 deklarieren und dann immer die Integer Variante von InterlockedExchange benutzen dürfte die sicherste Methode sein oder? Gibt es analog hierzu auch eine Möglichkeit "ranged types" auf eine bestimmte Größe zu forcen?
Delphi-Quellcode:
type
TRangedType = 0..1023; |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Delphi-Quellcode:
TFoo = class
private FCS : TCriticalSection; FValue : string; function GetValue : string; procedure SetValue(const Value : string) : string; public constructor Create; destructor Destroy; override; property Value : string read GetValue write SetValue; end; constructor TFoo.Create; begin inherited; FCS := TCriticalSection.Create; end; destructor TFoo.Destroy; begin FreeAndNil( FCS ); inherited; end; function GetValue : string; begin FCS.Enter; try Result := FValue; finally FCS.Leave; end; end; procedure SetValue(const Value : string) : string; begin FCS.Enter; try FValue := Value; finally FCS.Leave; end; end; |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Klar, das ist sicherlich ein brauchbarer Ansatz (den ich auch partiell schon implementiert hatte). Allerdings habe ich gelesen, dass Critical Sections nicht umbedingt super performant sind. Zumindest bei den Werten, die alle paar Milisekunden aktualisiert werden, wollte ich lieber die Interlocked Funktionen verwenden.
Kann man bezüglich der "ranged types" was machen, oder bin ich hier gezwungen Critical Sections zu benutzen? |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Delphi-Quellcode:
, oder, oder, oder, oder, ..... )
TMonitor
Welchen, das hängt eben davon ab, was da atomar ablaufen soll/muss. Ein
Delphi-Quellcode:
geht eben nur für einen Wert und eben nur für bestimmte Typen
TInterlocked
![]() |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Und die Synchronisationsobjekte (unter System.SyncObjs) leiten sich doch alle von einer gemeinsamen Oberklasse ab. Wenn du später eine die TCriticalSection gegen etwas anderes austauschen willst, ist das nur eine Sache- Ich hätte Sir Rufos Ansatz noch um eine Typdefinition erweitert:
Delphi-Quellcode:
Dann kannst du später alles in einer Zeile austauschen.
TFoo = class
protected type TSyncObj = TCriticalSection; private FCS : TSyncObj; FValue : string; function GetValue : string; procedure SetValue(const Value : string) : string; public constructor Create; destructor Destroy; override; property Value : string read GetValue write SetValue; end; |
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Zitat:
Bin momentan am sichten, auf welche Felder in welcher Form von wo aus zugegriffen wird. Momentan habe ich vier verschiedene Modi:
Hoffe ich habe da grade keine Logikfehler eingebaut. Zitat:
|
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
|
AW: TInterlocked.Exchange bei Zugriff eines nur lesenden Threads?
Habe mir jetzt für die primitiven Typen mal folgendes Konstrukt gebastelt. Damit kann ich alle Ordinaltypen, sowie Enums und Booleans meinen Vorstellungen entsprechend (und sehr performant) synchronisieren:
Delphi-Quellcode:
{$DEFINE ATOMIC_TYPE_CHECK}
{$IFDEF ATOMIC_TYPE_CHECK} const AllowedAtomicTypes = [tkEnumeration, tkRecord, tkFloat, tkInteger, tkChar, tkWChar]; AllowedAtomicTypes64 = [tkEnumeration, tkRecord, tkFloat, tkInt64]; {$ENDIF} type {** * @brief Implements an atomic ordinal value with a maximum size of 4 bytes. * } TAtomicOrdinal<T> = record strict private type PUInt32 = ^UInt32; PType = ^T; strict private FValue: UInt32; strict private procedure CheckRequirements; inline; procedure CheckArithmetical; inline; public {** * @brief Returns the value of the current @c TAtomicOrdinal instance. This operation is NOT * atomic. * @return The value of the current @c TAtomicOrdinal instance. * * Use this method only, if no other thread can ever change the value of the current * @c TAtomicOrdinal instance at the same time. * } function GetValue: T; inline; {** * @brief Returns the value of the current @c TAtomicOrdinal instance. This operation is * atomic. * @return The value of the current @c TAtomicOrdinal instance. * } function AtomicGetValue: T; inline; {** * @brief Sets the value of the current @c TAtomicOrdinal instance. This operation is NOT * atomic. * * Use this method only, if no other thread can ever read or change the value of the current * @c TAtomicOrdinal instance at the same time. * } procedure SetValue(const Value: T); inline; {** * @brief Sets the value of the current @c TAtomicOrdinal instance. This operation is * atomic. * } procedure AtomicSetValue(const Value: T); inline; public {** * @brief Exchanges the value of the current @c TAtomicOrdinal instance. This operation is * atomic. * @param Value The new value. * @return The old value of the current @c TAtomicOrdinal instance. * } function AtomicExchangeValue(Value: T): T; inline; function AtomicCompareExchangeValue(NewValue: T; Comparand: T): T; inline; public {** * @brief Adds to the value of the current @c TAtomicOrdinal instance. This operation is * atomic. * @param Value The summand. * * Do not use this method for non-integer types. * } procedure AtomicAdd(Value: T); inline; {** * @brief Subtracts from the value of the current @c TAtomicOrdinal instance. This operation * is atomic. * @param Value The subtrahend. * * Do not use this method for non-integer types. * } procedure AtomicSubtract(Value: T); inline; {** * @brief Increments the value of the current @c TAtomicOrdinal instance by one. This * operation is atomic. * * Do not use this method for non-integer types. * } procedure AtomicInc; inline; {** * @brief Decrements the value of the current @c TAtomicOrdinal instance by one. This * operation is atomic. * * Do not use this method for non-integer types. * } procedure AtomicDec; inline; public {** * @brief Implicit cast to the generic type. This operation is atomic. * @param A The @c TAtomicOrdinal type * @return The generic type value. * } class operator Implicit(A: TAtomicOrdinal<T>): T; inline; {** * @brief Equality check. The read operation from the @c A instance is atomic. The * comparison itself works with a temporal snapshot of the @c A value. * @param A The @c TAtomicOrdinal type * @param B The generic type to compare with. * @return True, if the values are equal, false if not. * } class operator Equal(A: TAtomicOrdinal<T>; B: T): Boolean; inline; end; {** * @brief Implements an atomic ordinal value with a size of 8 bytes. * } TAtomicOrdinal64<T> = record strict private type PUInt64 = ^UInt64; PType = ^T; strict private FValue: UInt64; strict private procedure CheckRequirements; inline; procedure CheckArithmetical; inline; public {** * @brief Returns the value of the current @c TAtomicOrdinal64 instance. This operation is * NOT atomic. * @return The value of the current @c TAtomicOrdinal64 instance. * * Use this method only, if no other thread can ever change the value of the current * @c TAtomicOrdinal64 instance at the same time. * } function GetValue: T; inline; {** * @brief Returns the value of the current @c TAtomicOrdinal64 instance. This operation is * atomic. * @return The value of the current @c TAtomicOrdinal64 instance. * } function AtomicGetValue: T; inline; {** * @brief Sets the value of the current @c TAtomicOrdinal64 instance. This operation is NOT * atomic. * * Use this method only, if no other thread can ever read or change the value of the current * @c TAtomicOrdinal64 instance at the same time. * } procedure SetValue(const Value: T); inline; {** * @brief Sets the value of the current @c TAtomicOrdinal64 instance. This operation is * atomic. * } procedure AtomicSetValue(const Value: T); inline; public {** * @brief Exchanges the value of the current @c TAtomicOrdinal64 instance. This operation is * atomic. * @param Value The new value. * @return The old value of the current @c TAtomicOrdinal64 instance. * } function AtomicExchangeValue(Value: T): T; inline; function AtomicCompareExchangeValue(NewValue: T; Comparand: T): T; inline; public {** * @brief Adds to the value of the current @c TAtomicOrdinal64 instance. This operation is * atomic. * @param Value The summand. * * Do not use this method for non-integer types. * } procedure AtomicAdd(Value: T); inline; {** * @brief Subtracts from the value of the current @c TAtomicOrdinal64 instance. This operation * is atomic. * @param Value The subtrahend. * * Do not use this method for non-integer types. * } procedure AtomicSubtract(Value: T); inline; {** * @brief Increments the value of the current @c TAtomicOrdinal64 instance by one. This * operation is atomic. * * Do not use this method for non-integer types. * } procedure AtomicInc; inline; {** * @brief Decrements the value of the current @c TAtomicOrdinal64 instance by one. This * operation is atomic. * * Do not use this method for non-integer types. * } procedure AtomicDec; inline; public {** * @brief Implicit cast to the generic type. This operation is atomic. * @param A The @c TAtomicOrdinal64 type * @return The generic type value. * } class operator Implicit(A: TAtomicOrdinal64<T>): T; inline; {** * @brief Equality check. The read operation from the @c A instance is atomic. The * comparison itself works with a temporal snapshot of the @c A value. * @param A The @c TAtomicOrdinal64 type * @param B The generic type to compare with. * @return True, if the values are equal, false if not. * } class operator Equal(A: TAtomicOrdinal64<T>; B: T): Boolean; inline; end; TAtomicUInt8 = TAtomicOrdinal<UInt8>; TAtomicUInt16 = TAtomicOrdinal<UInt16>; TAtomicUInt32 = TAtomicOrdinal<UInt32>; TAtomicUInt64 = TAtomicOrdinal64<UInt64>; TAtomicInt8 = TAtomicOrdinal<Int8>; TAtomicInt16 = TAtomicOrdinal<Int16>; TAtomicInt32 = TAtomicOrdinal<Int32>; TAtomicInt64 = TAtomicOrdinal64<Int64>; TAtomicBoolean = TAtomicOrdinal<LongBool>; TAtomicSingle = TAtomicOrdinal<Single>; TAtomicDouble = TAtomicOrdinal<Double>; { TAtomicOrdinal<T> } procedure TAtomicOrdinal<T>.AtomicAdd(Value: T); begin CheckRequirements; CheckArithmetical; AtomicIncrement(FValue, PUInt32(@Value)^); end; function TAtomicOrdinal<T>.AtomicCompareExchangeValue(NewValue, Comparand: T): T; var Value: UInt32; begin CheckRequirements; Value := AtomicCmpExchange(FValue, PUInt32(@NewValue)^, PUInt32(@Comparand)^); Result := PType(@Value)^; end; function TAtomicOrdinal<T>.AtomicExchangeValue(Value: T): T; begin CheckRequirements; Result := PType(AtomicExchange(FValue, PUInt32(@Value)^))^; end; function TAtomicOrdinal<T>.AtomicGetValue: T; begin CheckRequirements; Result := PType(@FValue)^; end; class operator TAtomicOrdinal<T>.Implicit(A: TAtomicOrdinal<T>): T; begin Result := A.AtomicGetValue; end; procedure TAtomicOrdinal<T>.AtomicInc; begin CheckRequirements; CheckArithmetical; AtomicIncrement(FValue); end; procedure TAtomicOrdinal<T>.SetValue(const Value: T); begin CheckRequirements; FValue := PUInt32(@Value)^; end; procedure TAtomicOrdinal<T>.AtomicSubtract(Value: T); begin CheckRequirements; CheckArithmetical; AtomicDecrement(FValue, PUInt32(@Value)^); end; procedure TAtomicOrdinal<T>.CheckArithmetical; begin {$IFDEF ATOMIC_TYPE_CHECK} Assert(PTypeInfo(TypeInfo(T))^.Kind = tkInteger, 'Arithmetical operations are only valid for integer types.'); {$ENDIF} end; procedure TAtomicOrdinal<T>.CheckRequirements; begin {$IFDEF ATOMIC_TYPE_CHECK} Assert(PTypeInfo(TypeInfo(T))^.Kind in AllowedAtomicTypes, 'Unsupported generic type.'); {$ENDIF} Assert(SizeOf(T) <= 4, 'The generic ordinal type exceeded the maximum of 4 bytes.'); Assert((UIntPtr(@FValue) mod 4) = 0, 'Value is not aligned on a 32 bit boundary.'); end; class operator TAtomicOrdinal<T>.Equal(A: TAtomicOrdinal<T>; B: T): Boolean; var Value: T; begin Value := A.AtomicGetValue; Result := PUInt64(@Value)^ = PUInt64(@B)^; end; procedure TAtomicOrdinal<T>.AtomicSetValue(const Value: T); begin CheckRequirements; AtomicExchange(FValue, PUInt32(@Value)^); end; procedure TAtomicOrdinal<T>.AtomicDec; begin CheckRequirements; CheckArithmetical; AtomicDecrement(FValue); end; function TAtomicOrdinal<T>.GetValue: T; begin CheckRequirements; Result := PType(@FValue)^; end; { TAtomicOrdinal64<T> } procedure TAtomicOrdinal64<T>.AtomicAdd(Value: T); begin CheckRequirements; CheckArithmetical; AtomicIncrement(FValue, PUInt64(@Value)^); end; function TAtomicOrdinal64<T>.AtomicCompareExchangeValue(NewValue, Comparand: T): T; var Value: UInt32; begin CheckRequirements; Value := AtomicCmpExchange(FValue, PUInt64(@NewValue)^, PUInt64(@Comparand)^); Result := PType(@Value)^; end; function TAtomicOrdinal64<T>.AtomicExchangeValue(Value: T): T; begin CheckRequirements; Result := PType(AtomicExchange(FValue, PUInt64(@Value)^))^; end; function TAtomicOrdinal64<T>.AtomicGetValue: T; {$IFDEF CPU64} begin Result := GetValue; {$ELSE} var Value: UInt64; begin CheckRequirements; Value := AtomicCmpExchange(FValue, 0, 0); Result := PType(@Value)^; {$ENDIF} end; class operator TAtomicOrdinal64<T>.Implicit(A: TAtomicOrdinal64<T>): T; begin Result := A.AtomicGetValue; end; procedure TAtomicOrdinal64<T>.AtomicInc; begin CheckRequirements; CheckArithmetical; AtomicIncrement(FValue); end; procedure TAtomicOrdinal64<T>.SetValue(const Value: T); begin CheckRequirements; FValue := PUInt64(@FValue)^; end; procedure TAtomicOrdinal64<T>.AtomicSubtract(Value: T); begin CheckRequirements; CheckArithmetical; AtomicDecrement(FValue, PUInt64(@Value)^); end; procedure TAtomicOrdinal64<T>.CheckArithmetical; begin {$IFDEF ATOMIC_TYPE_CHECK} Assert(PTypeInfo(TypeInfo(T))^.Kind = tkInt64, 'Arithmetical operations are only valid for integer types.'); {$ENDIF} end; procedure TAtomicOrdinal64<T>.CheckRequirements; begin {$IFDEF ATOMIC_TYPE_CHECK} Assert(PTypeInfo(TypeInfo(T))^.Kind in AllowedAtomicTypes64, 'Unsupported generic type.'); {$ENDIF} Assert(SizeOf(T) = 8, 'The generic ordinal type is smaller or greater than 8 byte.'); Assert((UIntPtr(@FValue) mod 8) = 0, 'Value is not aligned on a 64 bit boundary.'); end; class operator TAtomicOrdinal64<T>.Equal(A: TAtomicOrdinal64<T>; B: T): Boolean; var Value: T; begin Value := A.AtomicGetValue; Result := PUInt64(@Value)^ = PUInt64(@B)^; end; procedure TAtomicOrdinal64<T>.AtomicSetValue(const Value: T); begin CheckRequirements; AtomicExchange(FValue, PUInt64(@Value)^); end; procedure TAtomicOrdinal64<T>.AtomicDec; begin CheckRequirements; CheckArithmetical; AtomicDecrement(FValue); end; function TAtomicOrdinal64<T>.GetValue: T; begin CheckRequirements; Result := PType(@FValue)^; end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:34 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