Synchronisieren im FAlle einer Funktion
-
Hi.
Also Annahme: Ich habe eine Klasse FooBar. Davon ein Objekt foo.
Weiterhin zwei Threads. Beide kennen foo.
In FooBar sind Funktionen definiert. Nehmen wir eine Set_Text(STRINGTYPE) Funktion an, die eine Membervariable m_lol setzt.
Nun habe ich ein Synchronisationsobjekt syncher. Welcher Typ sei hier mal freigestellt. Handelt sich bei mir zwar um ein VCL Objekt , aber das Prinzip ist ja allgemeingültig.
So. Meine Frage ist nun:
Beide Threads rufen Set_Text auf. Nun muss ich m_lol absichern. Die Frage ist nun WIE.
foo->syncher->lock(); foo->Set_Text("AUA"); foo->syncher->unlock(); oder geht auch foo->Set_Text("AUA"); und dann INNHERBALB der Funktion void Set_Text(STRINGTYPE) { foo->syncher->lock(); m_lol=STRINGTYPE; foo->syncher->unlock(); }Möglichkeit A warten die Threads ja vor dem Aufruf. Bei B rufen sie auf und warten in der Funktion.
Aber ich weiss nicht, wie das ist... Kann ich zwei Threads gleichzeitig in eine Memberfunktion schicken und dort dann vor die rote Ampel parken?
Solche Dingelchen sind in keinem Buch geschrieben... Und das ist für mich eigentlich eine grundsätzliche Frage...
Da ich sowieso bei bin, poste ich gleich ein weiteres Problem:
Nehmen wir an ich habe eine weitere Memberfunktion wie bei der Konstlelation oben. Diese Memberfunktion schickt aber lediglich eine Message an eine MEssage
Queue eines Threads. Muss ich diese Funktion auch sichern? Oder wie ist das grundsätzlich, wenn zwei Threads die gleiche Memberfunktion aufrufen. Thread A ist in der Funktion unterwegs. Thread B ruft selbige Fu nktion auf. Arbeiten die dann beide diese funktion normal ab oder kommen die durcheinander. Also davon ausgegangen, daß diese funktion keine Variable ändert oder liest. Sondern nur IF Entscheidungen drin hat. Wenn das nicht geht fgreift anschliessend meine erste Frage. Ausserhalb der Funktion mit einem Mutex sichern oder innerhalb.
-
Erstens: technisch sind beide Versionen gleichwertig - der langsamere Thread wird beim lock()-Aufruf angehalten, bis der Kollege fertig ist mit der Arbeit. Aber Variante B ist eleganter, weil du dich als Anwender nicht darum kümmern mußt, den Zugriff zu sichern.
Zweitens: Synchronisieren mußt du nur, wenn die Threads sich gegenseitig in die Quere kommen könnten. Die Memberfunktion an sich ist kein Problem (von lokalen Variablen bekommt sowieso jeder eine eigene Version), kritisch wird es nur, wenn sie auf gemeinsam verfügbare Daten angewiesen sind.
-
Hab da auch mal ne Frage zu

Eigentlich wird es doch nur kritisch, wenn beide Threads schreibend auf dieselbe (nicht-lokale) Variable zugreifen oder? Wenn beide draus lesen, dürfte ja nix passieren. Wenn einer liest und einer schreibt, dürfte ja eigentlich auch nix passieren, wenn das Ergebnis als Ganzes und mit einem Flutsch reingeschrieben wird. Dann liefert das gleichzeitige Auslesen halt den neuen oder den alten Wert aber keinen undefinierten.
-
Aber ich weiss nicht, wie das ist... Kann ich zwei Threads gleichzeitig in eine Memberfunktion schicken und dort dann vor die rote Ampel parken?
Ganz klar: ja.
Thread A ist in der Funktion unterwegs. Thread B ruft selbige Fu nktion auf. Arbeiten die dann beide diese funktion normal ab oder kommen die durcheinander.
Wie der Code "heisst" den Thread A und B ausführen ist egal, wichtig ist nur was er tut. Also ob du z.B. 2 Funktionen hast die beide auf "X" zugreifen, oder eine einzige die von beiden Threads ausgeführt wird ist vollkommen egal.
Eine Funktion die nur auf Parameter/Lokale Variablen zugreift, bzw. auf Member die nach der Konstruktion niemehr geändert werden, kann ruhig aus zig Threads gleichzeitig aufgerufen werden. Vorausgesetzt natürlich alle Unterfunktionen die diese Funktion aufruft sind auch threadsafe. Beispiel:class Foo { public: virtual void Bar(int x); }; class Baz { public: Baz(Foo& foo) : m_foo(foo), m_x(rand()) {} void Test(int x) { int l = m_x * m_x; if (x != l) m_foo.Bar(x); } private: Foo& m_foo; int const m_x; };Obiges ist alles threadsafe, vorausgesetzt die Implementierung von "Foo::Bar" ist es auch. Dass dabei mehrere Threads gleichzeitig in "Baz::Test" sein können ist egal.
Wäre m_x aber z.B. nicht const müsste man Zugriffe darauf synchronisieren, auch nur lesende Zugriffe.
-
@Shogun
Du machst das schon alles ganz richtig; nur ist
natürlich besser da du- Codeduplizierung vermeidest
- Ein Übersehen der Synchronisierungsanforderung beim Aufruf ausschliesst.Allerdng solltest Du keine Annahmen über die Grnularität von Operationen machen;
ob eine Zuweisung wirklich atomar ist hängt von der Maschine und dem Compiler ab.Ferner gibt es dazu ja wohl auch eine "Get-Funktion"; wie synchst Du die denn mit der "Set-Funktion"?
@all:
Mir fällt auf dass viel Konfusion um die Threadsicherheit von Klasseninstanzen besteht; manchmal vermute ich dass dies daran liegt dass Threads oft selber in Klassen gekapselt werden.Mir erscheint das immer ein wenig künstlich; threads nutzen oder "durchlaufen" Klassen- (z.B. new) und Instanzmethoden aber ich hab immer methodische Bedenken gegen eine "class Thread" (zumindest ausserhalb von OS-Kernels).
Eine "class ThreadFactory" oder "class ThreadModerator" (meist Singletons) die irgendwelche Handles und Operationen damit anbieten finde ich viel plausibler.Wie seht Ihr das?
Grüsse
*this
-
Ich würde fast immer die erste Variante bevorzugen:
foo->syncher->lock(); foo->Set_Text("AUA"); foo->syncher->unlock();Die Ausnahme, die ich mir denken kann, ist dass FooBar außschliesslich von mehreren Threads benutzt wird und, dass das Synchronisationsobjekt nur von foo benutzt wird.
Man erlaubt den Benutzer damit, selbst zu enstscheiden, wann er einen Bottleneck (Lock()) einführt. Meistens weiß er das sowieso besser. Wird das Synchronisationsobjekt von anderen Objekten noch benutzt, so muß man keine zusätzliche Variante von Set_Text für den Fall schreiben, dass man sich schon in einem Bottleneck befindet.
Das Set_Text hört sich nach einer GUI Klasse an. Erlaubt dein GUI Framework, das Verändern von GUI Elementen von anderen Threads aus? In Qt ist das zumindest problematisch, wenn man auch noch ein Update der GUI haben möchte, denn die Anzeigemethoden setzen definitiv kein Mutex zum auslesen. Da ist es besser ein Event oder ein Signal über Threadgrenzen hinweg zu benutzen.
-
Ok ich antworte mal stückchenweise und editiere dann immer.
Es ist zu warm um am Stü ck zu denken.@Ponto: Also genaugenommen ist in diesem Speziellen Fragefall SetText fiktiv. Der Grund warum ich frage ist eine eigene Klasse zur Kapselung von Daten. Ich verwende ein TListView zum Aneigen von DAten. Diese Daten werden durch eine Funktion hinzugefügt, die von externen Threads aufgerufen wird. Diese Funktion blockt über ein Synchobjekt dann erstmal den Zugriff auf TListView kurz. Die konkrete Problemstellung kam, weil ich zum Abspeichern meiner Daten einen eigenen Thread möchte. Nur wie sollte ich die DAten rüber bringen. Zum Einen bringe ich es irgendwie nicht fertig Objekte vom Typ TListItem oder TListItems ohne Owner zu erzeugen. Und da Das ganze Sowieso Anzeigeelemente in der VCL sind, dachte ich mir dann das ist sowieso nicht gerade die feine englische Anzeigeelemente als Datenspeicher zu missbrauchen.
Also habe ich jetzt eine Datenstruktur dazwischen gepackt. Das ist mal eben eine kleine Klasse, die ein TDateTime Objekt kapselt und 4 AnsiStrings. Aus dieser Klasse erzheuge ich nun ein Array als Datenspeicher. Genau genommen zwei Arrays. Während A mit Daten gefüllt wird von eben der Funktion die ich erwähnte, werden
zwei Zeiger von B an den Speicher Thread übergeben, der die Daten von B in eine Datei packt. Ok geht auch kurz.... Double Buffer... Einer wird gefüllt und von der Anzeige ausgelesen, einer wird zum Sichern ausgelesenDas Füllen und von der Anzeige auslesen passiert im gleichen Thread (TForm). Das möcjhte ich dann, sicher ist sicher, eventuell mit einem TSynchObject oder TMultipleReadSingleWriteetcblabla sichern.
Aus diesem Grunde, und wegen anderen Programmteilen, frage ich hier. Ich möchte das Ganze allerdings allgemein halten und möglichst nicht Problembezogen. Denn ich denke, daß diese Frage viele beschäftigt. Meine Professoren haben immer gesagt hat EINER eine Frage, haben MEHRERE diese Frage.
Vielleicht entsteht hier die Grundlage für einen Artikel im Magazin. Denn im Web habe ich wirklich nciths zu dieser Thematik gefunden. Meine Bücher haben mir auch nicht wirklich etwas verraten.
Mit deiner Frage nach den GUIs hast du unbewusst ein weiteres meiner Probleme erwischt. In einer anderen Konstellation kämpfe ich auch mit der Problematik der Threadsicherheit. Auch dort geht es um die VCL. Ich habe ein TForm als grafische Visualisierung. Parallel dazu laufen diverse Threads. Die Threads stecken in einer Klasse. Ein Objekt dieser Klasse wurde in TForm definiert.
Nun habe ich diverse Threads. Die GUI eingeschlossen. Die Kommunikation erfolgt über ein Objekt einer Klasse, das die Adressen ALLER Threads übergeben bekommen hat. Will ein Thread einem anderen etwas sagen, so ruft er in dieser Klasse eine Funktion auf, die dann ein Post Message macht, ein SetEvent oder eben etwas an einem Element auf der GUI ändert. Das Einzige was darin bisher abgesichert wurde, sind die Set Events. Da existiert in jedem Thread ein SynchObject zu jedem Event.
Aber die Post Messages und die Änderungen an den GUI Elementen '(Beispielsweise eine Checkbox haken oder nicht haken) sind bisher nicht gesichert.Diese Problemstellung ist der echte Grund für meine fragen. Denn da flitzen 4 oder 5 Threads in einer Funktion rum, die sagen wir mal 10 IF Entscheidungen drin hat. Die IFs haben als Bedingung eben einen Identifier, der die Threads dann in den Code der If reinlässt oder nicht.
Nur... Eine If ist doch auch wieder ein Lesezugriff oder? Muss ich doch auch sichern. (Wieder so ne Frage... Ihr seht.... Sowas ist ständig).
So die nächsten Posts lese ich anch dem Essen auch nochmal genau durch. Soweit ich beim Überfliegen gesehen habe habt ihr genau das getroffen was mein Problem ist.
Also wie ich bereits andeutete... Ein Artikel, unter Umständen auch gemeinsam erarbeitet, über die DOs n DON'Ts bei Zugriffen auf Variablen, Funktionen oder Klassenelementen wäre Klasse.