![]() |
Critical Section um globale Methode?
Ich habe eine Methode, die in einer Unit mit global verfügbare Klassen und Methoden liegt.
Die Methode behandelt Clipboard-Operationen. Hierbei kann es zu Konflikten und folgender Fehlermeldung kommen: "Zwischenablage Zugriff verweigert kann nicht geöffnet werden" Nach meiner Recherche kommt es zu dieser Meldung, wenn zwischen Clipboard.Open und Clipboard.Close ein weiterer Zugriff auf die Zwischenablage erfolgt. Als Lösung viel mir als erstes ein, dass der Code von Clipboard.Open und Clipboard.Close eigentlich ein kritischer Abschnitt ist. Leider kenne ich mich mit der Implementierung von critical sections unter Delphi nicht so gut aus. Wie ließe sich das in diesem Kontext implementieren? Oder gibt es andere Lösungen für das Problem? Hier mal etwa Code:
Delphi-Quellcode:
Wäre es zb. möglich eine Unit-globale Variable vom Typ CriticalSection zu deklarieren und diese dann in SaveClipboard zu verwenden?
Unit GlobalStuff;
... function SaveClipboard : TList; ... function SaveClipboard : TList; begin ... ClipBoard.Open; ... ClipBoard.Close; ... end; |
AW: Critical Section um globale Methode?
Als erstes fiele mir auf jeden Fall ein Das Open/Close erst einmal mit einem
Delphi-Quellcode:
abzusichern ;-)
try..finally
Ein kritischer Abschnitt oder ähnliche Sicherungsmechanismen helfen dir eigentlich nur wenn du genau diese Methode aus verschiedenen Threads gleichzeitig aufrufen willst. Wenn dir da irgendein anderer Mechanismus für den Zwischenablagen-Zugriff reinfunkt (z.B. durch Drittanbieter-Komponenten oder ähnliches) hilft dir das auch nichts. |
AW: Critical Section um globale Methode?
Das stimmt. Die Vermutung besteht aber, dass verschiedene Threads unseres Programmes auf die Methode zugreifen. Das ist theoretisch möglich, da die Methode wie gesagt global verfügbar ist. Ob das aber wirklich so passiert, kann ich nicht sagen. Ein Kunde hat das Problem berichtet, reproduziert bekomme ich es nicht :wink:
Daher wäre ohnehin jede Lösung, die ich einbaue, ein Schuss ins Blaue. |
AW: Critical Section um globale Methode?
Soweit wir noch nicht bei Science Fiction sind sollte dein Programm auch nur das tun was im Quelltext steht. Schau doch einfach an welchen Stellen im Code die Methode aufgerufen wird und nicht "theoretisch könnte das sein" ;-)
Critical Sections sind einfach: Du erzeugst dir einmalig so ein Objekt, und immer um einen Abschnitt zu sperren sagst du einmal
Delphi-Quellcode:
und wenn du fertig bist
Acquire()
Delphi-Quellcode:
Release()
![]()
Delphi-Quellcode:
uses
System.SyncObjs; var criticalSection: TSynchroObj; procedure funWithClipboard(); begin criticalSection.Acquire(); try ClipBoard.Open(); try // (...) finally ClipBoard.Close(); end; finally criticalSection.Release(); end; end; PS: Software die das Haus verlässt braucht unbedingt vernünftiges Exception-Logging sodass du direkt den kompletten Aufruf-Stack bekommst. Wenn die einzige Info nur ein kurzer Text auf einer Messagebox ist wird man echt nicht glücklich. |
AW: Critical Section um globale Methode?
Zitat:
Zitat:
Zitat:
|
AW: Critical Section um globale Methode?
Viel einfacher:
Delphi-Quellcode:
Schneller als eine Critical Section, genau auf das Objekt bezogen und ohne zusätzliche Initialisierung usw.
TMonitor.Enter(ClipBoard);
try ... finally TMonitor.Exit(ClipBoard); end; |
AW: Critical Section um globale Methode?
Zitat:
![]() Meines Wissens nach versucht die Windows Implementation der Critical Section mitlerweile auch erstmal ein SpinLock, bevor es dann den teuren Context-Switch in den Kernel gibt. Sollte also nun sogar noch performanter sein. |
AW: Critical Section um globale Methode?
In deinem Link steht doch genau die Antwort auf die Berichte über die schlechte Performance. Seit XE5 ist das behoben und schneller als eine CS.
Hier auch nochmal der Link: ![]() |
AW: Critical Section um globale Methode?
Zitat:
![]() |
AW: Critical Section um globale Methode?
Müssen Clipboard Operationen nicht auch im Mainthread ablaufen? Dann wäre noch ein Synchronize nötig und könnte vielleicht auch schon alleine helfen.
|
AW: Critical Section um globale Methode?
Zitat:
Warning: Do not call Synchronize from within the main thread. This can cause an infinite loop. ![]() |
AW: Critical Section um globale Methode?
Zitat:
|
AW: Critical Section um globale Methode?
Zitat:
Zitat:
Delphi-Quellcode:
ja jetzt zumindest gleichwertig zur CriticalSection zu sein, wenn man einen adäquaten Wert für den SpinCount wählt.
TMonitor
|
AW: Critical Section um globale Methode?
Zitat:
|
AW: Critical Section um globale Methode?
Durch "
![]() ich verwende bis heute eine globale CriticalSection in meinem Programm, welche von vielen Threads aufgerufen wird, die dieselben Daten lesen und schreiben. Überhaupt gar kein Problem und die Performance sieht auch OK aus. Wie würde die Implementierung eines System.TMonitor aussehen? Wie das aussieht muss ein Object geblockt werden. Welches denn genau? Meine TThread-Klasse kann man ja nicht übergeben. Was möchte TMonitor denn sonst haben? Muss ich hier Self (TThread-Unit) übergeben oder reicht auch einfach eine Variable? Diese Variable (Klasseninstanz) enthält alles was ich im Thread schreibend ändere. |
AW: Critical Section um globale Methode?
Zitat:
Delphi-Quellcode:
nur als Marker. Oft hat man ja eine seperate Datenklasse, auf die die Threads dann zugreifen. Hier würde ich die Instanz dieser Klasse verwenden, aber im Grunde ist es komplett egal, welches Objekt du verwendest.
TMonitor
|
AW: Critical Section um globale Methode?
Genau. Ich habe eine Klasse (vorher war es ein Record).
Dieser Klasseninstanz, welche nur einmalig erzeugt wird, weise ich in den Threads ein Objekt zu mit welchem ich dann weiterarbeite. Das Objekt enthält Strings, Integers... ganz normale Dinge. Aktuell habe ich eine globale CS welche von allen 5 Threads verwendet wird. Klappt sehr fein. Keinerlei Konflikte. Könnte ich dann nun einfach auf System.TMonitor.Enter(meine globale Klasseninstanz) verwenden und hätte dasselbe Ergebnis nur besser? Was müsste ich dem Enter denn übergeben, wenn ich sowas hier habe
Delphi-Quellcode:
Dort steht aktuell auch CriticalSections. Ohne ging das schief. Würde aber gerne weg von CS und hin zu Monitor nur weiß ich nicht, was ich dort als Objekt übergeben soll.
- TTask Anfang (eine Aufgabe bei Klick im MainMenu)
Monitor Enter try EineFunktionWirdAufgerufen finally Monitor Exit end; - TTask Ende |
AW: Critical Section um globale Methode?
Im Prinzip ist TMonitor die umgedrehte Implementation für mehrere TCriticalSection.
Du kannst in ein anderes Object eine CriticalSection rein tun und diese verwenden
Delphi-Quellcode:
oder du nutzt das "versteckte" Feld in allen TObject, welches man über TMonitor ansprechen kann.
TMyObject = class
Lock: TCriticalSecition; end; MyObj.Lock.Enter
Delphi-Quellcode:
Nur den Namen "Monitor" fand ich schon immer ein bissl "irreführend".
TMyObject = class
end; TMonitor.Enter(MyObj); // macht dann intern quasi sowas wie ein MyObj.geheimesproperty.Enter; // und vorher noch ein IF NOT Assigned(MyObj.geheimesproperty) THEN MyObj.geheimesproperty := TCriticalSectionÄhnlichesDing.Create; |
AW: Critical Section um globale Methode?
Das Problem ist, dass ich für mein zuletzt genantes Beispiel keine Klasse habe und ich nur eine Funktion aufrufe.
Zitat:
Ich glaube ich bleibe einfach bei CS - ist einfacher und nicht so ein Hexenwerk ;) |
AW: Critical Section um globale Methode?
Zitat:
Statt deiner CS könntest du dir einfach eine dummy
Delphi-Quellcode:
Instanz erstellen und darauf dann
TObject
Delphi-Quellcode:
anwenden. Macht aber nicht wirklich Sinn, weshalb ich hier wohl auch bei der globalen CriticalSection bleiben würde.
TMonitor.Enter
|
AW: Critical Section um globale Methode?
Zitat:
Ist wie der unterschied zwischen
Delphi-Quellcode:
und
StringList[123]
Delphi-Quellcode:
.
StringList.Strings[123]
Abgesehn, dass TMonitor was "Eigenes" ist und TCriticalSection die API vom Windows kapselt, aber von der Funktion her sind die schon vergleichbar. |
AW: Critical Section um globale Methode?
Ok dann bleibe ich wohl bei CriticalSections.
Die sind mir vom Aufbau her so oder so logischer. Ich selber konnte bei meinen Tests keinerlei Performanceunterschied feststellen. Scheinen wohl nur Nanosekunden zu sein. |
AW: Critical Section um globale Methode?
In Delphi gibt es noch viel mehr Implementationen, die das Gleiche wie eine CriticalSection/TMonitor erledigen.
Dann noch paar Dinger mit Zusatzfunktionen, wie z.B. den TMultiReadExclusiveWriteSynchronizer (TMREWSync), welcher parallele Lesezugriffe erlaubt und nur bei einem Schreibzugriff alles komplett sperrt. Und für Zugriffe auf einzelne Interger, Pointer, Booleans und anderen Kleinkram gibt es mehrere Interlocked-Implementationen, welche atomare Schreibzugriffe ermöglichen. |
AW: Critical Section um globale Methode?
Zitat:
Aber heißt das jetzt, dass sobald ich bei Verwendung von TMultiReadExclusiveWriteSynchronizer irgendwo trotzdem schreibe, nur dieser eine schreibende Zugriff geblockt wird? Wenn ja dann wäre das ja ein recht großer Vorteil denn statt 100 von 100 "Dingern" zu blocken (wo ich 1x schreibe und 99x lese) würde hier nur 1x geblockt. Sehe ich das richtig? Dass ich schreibenden Zugriff hier mit BeginWrite und EndWrite absichern muss ist klar. Aber wofür den lesenden? Edit: durch die Verwendung eines globalen TMultiReadExclusiveWriteSynchronizer und BeginWrite/EndWrite, welches ich NUR dort verwende wo ich meiner Klasseninstanz Daten zuweise, ist meine Prozedur rund 5% schneller geworden. Kann das daran liegen, dass ich nun nicht mehr rigoros alles blockiere sondern nur noch den schreibenden Zugriff? |
AW: Critical Section um globale Methode?
Zitat:
Delphi-Quellcode:
ist für solche Szenarien besser geeignet, als
TMultiReadExclusiveWriteSynchronizer
Delphi-Quellcode:
oder
TCriticalSection
Delphi-Quellcode:
. Solange jetzt n-Threads gleichzeitig NUR lesen, wirst du einen Performancevorteil feststellen können. Im Grunde stellt der
TMonitor
Delphi-Quellcode:
jetzt nur noch sicher, dass erst alle lesenden Zugriffe abgeschlossen sind, bevor ein schreibender Zugriff stattfindet und blockiert während dieser Zeit dann alle lesenden Operationen (deshalb musst du trotzdem signalisieren, an welchen Stellen du lesen willst).
TMultiReadExclusiveWriteSynchronizer
Mit
Delphi-Quellcode:
oder
TCriticalSection
Delphi-Quellcode:
hingegen, kann auch nur ein einziger Thread gleichzeitig lesen.
TMonitor
|
AW: Critical Section um globale Methode?
Lesezugriffe sperren sich nicht gegenseitig ... mehrere sind erlaubt.
So lange es Lesesperren gibt, warten alle Schreibanfragen. Eine Schreibsperre sperrte alle anderen Lese und Schreibzugriffe. Natürlich nur verwendbar für "echte" Lesezugriffe, wo wirklich nichts geschrieben wird. z.B. Stream.Read ist zwar ein Lesezugriff, aber da bei wird der Positionszeiger geändert, was einen Schreibzugriff darstellt. Aber sonst können mehrere ungehindert gleichzeitig lesen und wenn Einer was schreiben will, dann ist so lange alles gesperrt. MultiReader ist demnach besonders hilfreich, bei vielen Lesezugriffen und seltenen Schreibzugriffen. Bei der CriticalSection und dem TMonitor ist immer alles gesperrt, egal ob lesen oder schreiben. (nur Zugriffe aus dem selben Thread sind erlaubt, da sie per se threadsave sind und man so keinen Deadlock riskiert, wenn man in einem Thread verschachtelt Enter aufruft) |
AW: Critical Section um globale Methode?
Mhhh ok das heißt also
Delphi-Quellcode:
So habe ich das aktuell. Jeder Thread kann der globale Klasseninstanz ein Item zuweisen. An anderen Stellen (nicht in diesem Thread) lese ich die Klasseninstanz dann aus und zeige die dahinterliegenden Daten auf der Benutzeorberfläche an.
-thread-instanzen 1+2+3 (alle selber Code, nur unterschiedliche Listen sind abzuarbeiten)
begin schleife anfang _globals.aMultiReadExclusiveWriteSynchronizer.BeginWrite; setze globale Klasseninstanz := Liste.Items[i] _globals.aMultiReadExclusiveWriteSynchronizer.EndWrite; (a) ... lese hier ... lese dort ... lese nochmals hier (b) schleife ende end; Ist das so richtig oder müsste ich jetzt noch zusätzlich von (a) bis (b) ein _globals.aMultiReadExclusiveWriteSynchronizer.Begi nRead; und EndRead setzen? |
AW: Critical Section um globale Methode?
Dort, wo lesend auf globale-Klasseninstanz zugegriffen wird, kommt natürlich ein BeginRead drumrum.
Erstmal reicht es den Lese-Zugriff auf diese Variable zu sperren, aber wenn sich innerhalb dieser Instanz auch etwas ändern kann (Property und enthaltene Variablen), dann auch das mit einschließen. Wenn sich an der Objektinstanz nicht gleichzeig in mehreren Thread was ändert, also z.B. da was einstellen, dann die Variable setzen und jemand Anderes liest dann nur diese Variable, oder holt sich sie da raus (auslesen und NIL setzen), dann kann man auch mit InterlockedExcange oder den neuen TLock-irgendwas-Klassen in der System-Unit arbeiten. |
AW: Critical Section um globale Methode?
Da achte ich strikt drauf. Erst ändere ich und dann lese ich. Danach wird erstmal nicht mehr geändert. Im jedem Schleifendurchgang gibt es nur eine einzige Änderung.
Wofür ist es denn wichtig den lesenden Zugriff zu sperren? Wenn man 5 Thread-Instanzen hat, alle haben die gleichen Dinge abzuarbeiten, man aber den lesen Zugriff blockieren muss, kann man dann hier überhaupt noch von Multithreading reden? Erste Tests zeigen, dass wenn ich überall den lesenden Zugriff blockiere der Code doppelt so lange braucht als vorher. Den lesenden Zugriff hatte ich früher noch nie blockiert, immer nur den schreibenden. |
AW: Critical Section um globale Methode?
Die Lesedinge sperren sich dabei nicht gegenseitig.
Das BeginRead ist nur dafür da, um zu verhindern, dass jemand während des Lesens etwas ändern kann. BeginRead sperrt das BeginWrite, bzw. hält selber an, wenn gerade geschrieben wird. Andere BeginRead werden aber durch den Read-Lock (durch BeginRead) nicht beeinflusst. |
AW: Critical Section um globale Methode?
Zitat:
Wenn BeginRead nur dafür da ist zu verhindern, dass kein Write ausgeführt werden kann, lasse ich das vermutlich weg denn es gibt genau eine einzige Stelle an der geschrieben wird. |
AW: Critical Section um globale Methode?
Zitat:
Oder du kannst nicht ausschließen, dass jemand liest während du schreibst, dann musst du dies durch Sperren sicherstellen, in dem Fall durch den TMultiReadExclusiveWriteSynchronizer. Dann musst du dem aber auch sagen wann du lesen und wann schreiben möchtest, damit er diese Sperre auch durchführen kann. |
AW: Critical Section um globale Methode?
Ist bei mir ganz einfach: es ist exakt eine einzige Zeile Code im Multithread-System, die schreibenden Zugriff hat.
Es gibt eine weitere solche Zeile, die hat aber nichts mit Multithreading zu tun. Es ist egal von wo aus ich TMultiReadExclusiveWriteSynchronizer aufrufe, oder muss das aus dem Thread heraus sein der schreiben möchte? |
AW: Critical Section um globale Methode?
Zitat:
Du musst vor jedem Schreibvorgang BeginWrite aufrufen (ab da werden alle Schreib- und Lesezugriffe blockiert) und nach dessen Abschluss EndWrite (dann können wieder alle lesen). Vor jedem Lesevorgang musst du BeginRead aufrufen (ab da wartet BeginWrite, falls es danach aufgerufen wird, auf den Abschluss des Lesevorgangs) und nach dessen Abschluss EndRead (danach kehrt BeginWrite zurück, falls es nach BeginRead aufgerufen wurde. Auf diese Weise kann ein Schreibvorgang erst nach Abschluss aller laufenden Lesevorgänge passieren und neue Lesevorgänge erst nach dem Abschluss eines laufenden Schreibvorgangs. Solange aber niemand schreibt, können alle parallel lesen, d.h. BeginRead..EndRead kann beliebig parallel laufen. Um zur Frage zurückzukommen: Im Prinzip ist es egal aus welchem Threadkontext du BeginWrite..EndWrite aufrufst solange es vor bzw. nach dem Schreibvorgang passiert. Wann der passiert, sollte ein anderer Thread aber in der Regel (es gibt Ausnahmen, ja) gar nicht kontrollieren, weshalb die praktische Antwort meistens Nein ist, auch wenn es technisch egal ist. Genau lässt sich das aber nur sagen, wenn man den konkreten Ablauf kennt. Es würde auch abstrakt ohne Code genügen, z.B. auch als Diagramm, wer (Thread XY) wann (während andere Threads laufen? autark? kontrolliert durch einen anderen Thread? ...) was (Schreiben oder Lesen) macht, um einen Überblick zu haben. |
AW: Critical Section um globale Methode?
Hmm..
Der Zugriff erfolgt vergleichbar einer TCriticalSection. Beispiel:
Delphi-Quellcode:
unit Unit2;
interface uses SysUtils; function LeseDaten : string; procedure SchreibeDaten(Value : string); implementation var MSync_Global : TMultiReadExclusiveWriteSynchronizer; Daten_Test : string; function LeseDaten : string; begin MSync_Global.BeginRead; try Result := Daten_Test; finally MSync_Global.EndRead; end; end; procedure SchreibeDaten(Value : string); begin MSync_Global.BeginWrite; try Daten_Test := Value; finally MSync_Global.EndWrite; end; end; initialization MSync_Global := TMultiReadExclusiveWriteSynchronizer.Create; finalization MSync_Global.Free; end. Alle Threads (auch der Main..) nutzen nun einfach function LeseDaten : string; procedure SchreibeDaten(Value : string); für den Zugriff auf 'Daten_Test : string;'. Durch die Verwendung des globalen 'MSync_Global : TMultiReadExclusiveWriteSynchronizer;' innerhalb der Funktionen ist der Zugriff Threadsave.. ;) (Einfach nur mal so Runtergeschrieben als minimales Beispiel ;) ) (Fehler und Irtümer nicht ausgeschlossen ;) ) |
AW: Critical Section um globale Methode?
Zitat:
Delphi-Quellcode:
Wie man sieht gibt es genau eine einzige Stelle in dieser Thread-Unit, wo geschrieben wird. Alles danach ist nur noch lesen.
// Andere Unit erzeugt die Threads aus dem Mainthread heraus
erzeuge Thread 1+2+3 und gebe jedem ein Teil aus einer großen Liste; // (ja ich weiß, es gibt TParallel.For :P ) // Die Thread-unit selber -thread-instanzen 1+2+3 (alle selber Code, nur unterschiedliche Listen sind abzuarbeiten) begin schleife anfang _globals.aMultiReadExclusiveWriteSynchronizer.BeginWrite; setze globale Klasseninstanz := Liste.Items[i] _globals.aMultiReadExclusiveWriteSynchronizer.EndWrite; (a) ... lese hier ... lese dort ... lese nochmals hier (b) schleife ende end; |
AW: Critical Section um globale Methode?
Zitat:
Um das Sync zu machen, musst du dennoch von jedem Lesen 'BeginRead' und danach 'EndRead' machen! Nur fürs Schreiben BeginWrite/EndWrite reicht nicht! |
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:40 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