Design-Frage: Threadsave Classes?



  • makkurona schrieb:

    Ah, verstehe!
    Critical Sections klingen nach etwas ganz akzeptablem.

    Unter Windows sind zum Absichern Critical Sections und Mutexe vorgesehen, wobei man eher Critical Sections prozessintern verwendet, da sind sie auch schneller als Mutexe. Die SFML-Lösung verwendet für Windows innendrin ja auch Critical Sections (bist du von da drauf gekommen)?

    Allerdings stellt sich mir dabei die Frage, ob man die gesamte Klasse in eine solche Sektion packen sollte, und wie das ganze unter C++ überhaupt funktioniert, bzw welche Librarie sich dafür eigenet.

    Ich denke das ist der falsche Ansatz. Man sollte nicht einfach so eine ganze Klasse in eine CS packen. Man sollte eher vernünftig planen, welche Daten genau synchronisiert werden sollen. Dann wird der Zugriff auf diese Daten eben ummantelt.
    Libraries gibt's wie gesagt genug, ich verwende z.B. grad SFML zum MT-Absichern. Du lädst dir also einfach die Library runter, bindest die nötigen Include-Dateien und Bibliotheken ein und schon kannst du die Funktionen so verwenden, wie ich's oben gezeigt hab 🙂



  • Man brauch nicht für jeden Schrott zusätzliche Libs, die das Programm fett machen und unnötigen Fremdcode beinhalten. Er postet im WinAPI Forum, also kriegt er als Antwort das, was Windows zur Verfügung stellt. Wenn er wissen will, wie man unter Linux oder sonstwo synchronisiert, wird er schon im entsprechenden Forum fragen!

    Also hier nochmal, weil ich so hilfsbereit bin:
    http://msdn2.microsoft.com/en-us/library/ms682530.aspx
    http://msdn2.microsoft.com/en-us/library/ms686364(VS.85).aspx

    Wenn du das liest, bist du schlauer.



  • Witzig, der Thread war eben noch im WinAPI Forum. Könntet ihr solche Aprilscherze mal unterlassen@mods.



  • Man sollte sich auch sehr im klaren sein, dass jede synchronisierung potenziell das Programm verlangsammt. Ziel ist es also moeglichst wenig/keine syncpoints zu haben und entsprechend nicht blind alles zu schuetzen, sondern nur wenn es wirklich noetig ist. ansonsten machst du das programm vielleicht langsammer als mit nur einem thread.



  • Mhh, das ganze scheint ziemlich kompliziert zu sein - oder zumidnest komplizierter, als ich es mir vorgestelt hatte.

    Die Frage ist natürlich, nachdem ich eure Posts und Abschnittsweise weitere Literatur zu dem Thema, welche Daten genau geschützt werden müssen.

    Sicherlich schoneinmal zeigerlisten und globale Variablen, denke ich. Zeiger im generellen sollten höchstwahrscheinlich auch geschützt werden, nicht das man noch auf unvollständige Zeigeradressen zugreifen möchte und dadruch einen Segmentation Fault doer ähnliches erhält.

    Was allerdings noch zu schütznde daten sind... Wären das einfach alle Daten, die von mehreren Kernen gleichzeitig genutzt werden könnten?



  • rofler schrieb:

    Man brauch nicht für jeden Schrott zusätzliche Libs, die das Programm fett machen und unnötigen Fremdcode beinhalten. Er postet im WinAPI Forum, also kriegt er als Antwort das, was Windows zur Verfügung stellt. Wenn er wissen will, wie man unter Linux oder sonstwo synchronisiert, wird er schon im entsprechenden Forum fragen!

    Ich sehe das anders. Gerade wenn man alleine an einem Projekt arbeitet, ist jede geschriebene Zeile ein potenzieller Bug-Verursacher. Je mehr fremden und geprüften Code man verwendet, desto mehr sicheren Code hat man in seinem Projekt. Ich gehe jede Wette ein, dass Open-Source-Libs (die zumindest halbwegs bekannt/genutzt sind) weniger Fehler in sich bergen als eigen-geschriebener Code.
    Extrem-Beispiel Sockets: Wenn ich will, dass mein Programm vernünftig mit Sockets arbeitet, greife ich auf eine Lib zurück. Bevor ich Tage damit verbringe, Code zu schreiben, der im Endeffekt genauso auf die Klassen "Socket", "ServerSocket" und "ClientSocket" hinausläuft und höchstwahrscheinlich mehrere kleine Fehler birgt (z.B. auf irgendwelche Rückgabewerte nicht richtig reagiert), hole ich mir ich eine Library, die von hunderten anderen benutzt wird und sogar noch relativ plattformunabhängig ist.
    Ganz abgesehen davon, dass man sich eine Menge Zeit erspart, auch mit den Umsetzungen auf andere Betriebssysteme. Und "fett" werden Programme von fertigen Libs auch nicht, schließlich kommt nur der Code dazu, der auch gebraucht/verwendet wird.

    Oder von einer anderen Warte aus gesehen: Angenommen ich habe ein Programm, dass irgendwo bei der Netzwerkkommunikation einen Fehler hat. Wenn ich mit Fremd-Libs arbeite, habe ich x Zeilen Code, die mit der Kommunikation zu tun haben und sich eben der Lib bedienen. Und ich weiß, dass irgendwo in den x Zeilen der Fehler stecken muss.
    Habe ich alles selber geschrieben, habe ich noch ein paar hundert mehr Zeilen Code, die ich überprüfen muss.

    Oder noch anders: Ich plane ein Programm, dass sich dieser und jener und sonstiger Techniken bedient; nehmen wir einfach mal an, ich plane einen Musik-Player, der MP3s aus dem INet gestreamt bekommt und abspielen soll. Dann brauche ich dafür eine Kommunikationseinheit, MP3-Dekodierung und Audio-Ausgabe. Zusätzlich steht noch die Entscheidung, ob das Programm eine GUI hat und auf welchen BS es laufen soll.
    Nach eben diesen Anforderungen wähle ich also die Bausteine für mein Programm und muss diese im Idealfall nur noch zusammenstecken. Und eben solche Bausteine sind nun mal Libraries: Ich suche mir eine Comm-Lib, eine MP3-Decod-Lib, eine Audio-Lib und eine GUI-Lib. Wenn das Ganze nur auf Windows laufen soll, entscheide ich mich vielleicht auch für die MFC oder C#, je nachdem. Falls es Multi-Plattform werden soll, nehme ich C++ oder Java. Also suche ich mir die entsprechenden Libs zusammen und programmiere den "Verwaltungs"-Code dafür, also nur den Kern, der halt die entsprechenden Funktionen des Fremdcodes aufruft.
    So brauche ich wesentlich weniger Zeit und habe weniger Fehlerquellen.

    T'schuldigung für Offtopic, aber das musste sein.



  • makkurona schrieb:

    Was allerdings noch zu schütznde daten sind... Wären das einfach alle Daten, die von mehreren Kernen gleichzeitig genutzt werden könnten?

    Du musst dir erst überlegen, was du von der Logik deines Programms überhaupt parallelisieren willst. Und dann schaust du, was für Daten von mehr als einem Thread gleichzeitig benutzt werden, eben diese musst du dann schützen, siehe rapsos Post.



  • @ badestrand:
    Da liegt gerade das Problem: Jedes Element ist ein potentieller Anwärter auf gleichzeitige Änderung.
    Alles könnte in irgendeiner Weise gleichzeitig aufgerufen werden. Lesen und schreiben beispielsweise.

    Funktion 1 Schreibt die Position eines Objektes, Funktion 2 liest sie gerade aus.
    Das läuft auf Fehler hinaus, nicht wahr?^^"

    Nunja, ich werde mal schaun, eventuell kann ich ja das gesamtdesign etwas überdenken. Ich werde mal ein paar versuche zu starten 🙂



  • Warum sicherst du dann nicht die Aufrufe in die Klasse? Sagen wir mal, du hast zwei Threads, die beide auf derselben Instanz der Klasse arbeiten, dann sicherst du eben alle Aufrufe auf die Klasse ab und nicht innerhalb der Klasse. Etwa so:

    SomeClass cl;
    
    void task1()
    {
        while ( true )
        {
            ZugriffSichern()
            cl.foo()
            ZugriffFreigeben()
        }
    }
    
    void task2()
    {
        while ( true )
        {
            ZugriffSichern()
            cl.foo()
            ZugriffFreigeben()
        }
    }
    


  • Du meinst Quasi eine Funktion zu schreiben, die auf - was weiß ich - eine Lsite von Funktionszeigern zugreift, und die aufrufe nacheinander abarbeitet und dabei immer nur den Funktionsaufruf schützt?

    Führt das nicht zu Problemen, wenn innerhalb der aufgerufenen Funktion wiederum auf andere Objekte zugegriffen wird?
    Oder muss man dann für Jede einzelne Operation innerhalb dieser Sektionen einen pointer in die Lsite pushen?
    Klingt umständlich, nicht unbedingt schwierig, aber umständlich.

    (Die parameter für die Funktionsaufrufe könnte man ja in einem weiteren Vektor speichern, nur wie man die dann noch an die Funktion übergeben sollte ... )



  • makkurona schrieb:

    Du meinst Quasi eine Funktion zu schreiben, die auf - was weiß ich - eine Lsite von Funktionszeigern zugreift, und die aufrufe nacheinander abarbeitet und dabei immer nur den Funktionsaufruf schützt?

    Ne, überhaupt nicht 😃

    Hast du ein konkretes Anwendungsbeispiel mit Threads, dann könnten wir das daran vielleicht diskutieren? So _ganz_ allgemein ist schwierig.



  • Badestrand schrieb:

    rofler schrieb:

    Man brauch nicht für jeden Schrott zusätzliche Libs, die das Programm fett machen und unnötigen Fremdcode beinhalten. Er postet im WinAPI Forum, also kriegt er als Antwort das, was Windows zur Verfügung stellt. Wenn er wissen will, wie man unter Linux oder sonstwo synchronisiert, wird er schon im entsprechenden Forum fragen!

    Ich sehe das anders. Gerade wenn man alleine an einem Projekt arbeitet, ist jede geschriebene Zeile ein potenzieller Bug-Verursacher. Je mehr fremden und geprüften Code man verwendet, desto mehr sicheren Code hat man in seinem Projekt. Ich gehe jede Wette ein, dass Open-Source-Libs (die zumindest halbwegs bekannt/genutzt sind) weniger Fehler in sich bergen als eigen-geschriebener Code.
    Extrem-Beispiel Sockets: Wenn ich will, dass mein Programm vernünftig mit Sockets arbeitet, greife ich auf eine Lib zurück. Bevor ich Tage damit verbringe, Code zu schreiben, der im Endeffekt genauso auf die Klassen "Socket", "ServerSocket" und "ClientSocket" hinausläuft und höchstwahrscheinlich mehrere kleine Fehler birgt (z.B. auf irgendwelche Rückgabewerte nicht richtig reagiert), hole ich mir ich eine Library, die von hunderten anderen benutzt wird und sogar noch relativ plattformunabhängig ist.
    Ganz abgesehen davon, dass man sich eine Menge Zeit erspart, auch mit den Umsetzungen auf andere Betriebssysteme. Und "fett" werden Programme von fertigen Libs auch nicht, schließlich kommt nur der Code dazu, der auch gebraucht/verwendet wird.

    Oder von einer anderen Warte aus gesehen: Angenommen ich habe ein Programm, dass irgendwo bei der Netzwerkkommunikation einen Fehler hat. Wenn ich mit Fremd-Libs arbeite, habe ich x Zeilen Code, die mit der Kommunikation zu tun haben und sich eben der Lib bedienen. Und ich weiß, dass irgendwo in den x Zeilen der Fehler stecken muss.
    Habe ich alles selber geschrieben, habe ich noch ein paar hundert mehr Zeilen Code, die ich überprüfen muss.

    Oder noch anders: Ich plane ein Programm, dass sich dieser und jener und sonstiger Techniken bedient; nehmen wir einfach mal an, ich plane einen Musik-Player, der MP3s aus dem INet gestreamt bekommt und abspielen soll. Dann brauche ich dafür eine Kommunikationseinheit, MP3-Dekodierung und Audio-Ausgabe. Zusätzlich steht noch die Entscheidung, ob das Programm eine GUI hat und auf welchen BS es laufen soll.
    Nach eben diesen Anforderungen wähle ich also die Bausteine für mein Programm und muss diese im Idealfall nur noch zusammenstecken. Und eben solche Bausteine sind nun mal Libraries: Ich suche mir eine Comm-Lib, eine MP3-Decod-Lib, eine Audio-Lib und eine GUI-Lib. Wenn das Ganze nur auf Windows laufen soll, entscheide ich mich vielleicht auch für die MFC oder C#, je nachdem. Falls es Multi-Plattform werden soll, nehme ich C++ oder Java. Also suche ich mir die entsprechenden Libs zusammen und programmiere den "Verwaltungs"-Code dafür, also nur den Kern, der halt die entsprechenden Funktionen des Fremdcodes aufruft.
    So brauche ich wesentlich weniger Zeit und habe weniger Fehlerquellen.

    T'schuldigung für Offtopic, aber das musste sein.

    Ja, das ich mir eine Mp3 Decocder nicht selber schreibe oder bei Winsock ruhig auf das zurückgreife, wsa mir die WinAPI anbietet, das ist klar. Aber für Thread Synchronisation unter Windows brauche ich doch keine extra Library, für dieses dutzend Funktionen?



  • rofler schrieb:

    Aber für Thread Synchronisation unter Windows brauche ich doch keine extra Library, für dieses dutzend Funktionen?

    Es sei denn du bist klug, dann merkst du recht bald dass du dringend libraries brauchst.



  • So drastisch hätte ich es jetzt nicht ausgedrückt. Ich würde eher sagen, dass es im Endeffekt so oder so auf Libaries hinausläuft, ob eigene oder fremde. Nehmen wir einfach mal den Fall mit der Thread-Synchronisation mit Critical Sections.

    Es wird bei jedem Schutzzugriff darauf hinauslaufen, dass du für die zu schützenden Daten eine CRITICAL_SECTION brauchst, die du mit InitializeCriticalSection initialisieren musst und mit DeleteCriticalSection löschen musst, meistens im Con- bzw. Destrukor deiner Klasse. Zum schützen rufst du Enter-/LeaveCriticalSection auf. Nach 'ner Zeit wird das nerven und du baust dir eine kleine Klasse "CriticalSection", die sich automatisch ini- und de-initialisiert und einen "Lock" und "Unlock"-Aufruf hat, diese Klasse kommt irgendwo als "Helper" in das Projekt.
    Nach ein paar Projekten nervt das auch, jedesmal die Klasse da reinzukopieren oder sogar neu zu schreiben und du lagerst die Codedateien für die CriticalSection-Klasse irgendwo auf der Festplatte und bindest bei jedem Projekt die Header- und Cpp-Datei ein. Das einbinden der Cpp-Datei(en) nervt irgendwann auch und du kompilierst dir die Klasse zu einer Lib, so dass du nur noch #include "Meins/CriticalSection.h" schreibst und die Lib in den Compilereinstellungen hinzufügst.
    So kommt es m.E. früher oder später; und wenn du genau das für viele verschiedene Dinge der WinAPI gemacht hast, nimmst du dir vielleicht doch eine Lib, die das alles kann, und die du bei Bedarf auch einfach unter Linux oder Mac verwenden kannst.



  • Back To Topic pls.. wobei sonen Flamewar hat auch was, wird aber glaube ich nicht gerne gesehen. (:

    Ein Beispiel... mhh...
    Wir habe eine Klasse, die als Objekt fungiert. In ihr sind beispielsweise mehrere Vertexe, positionen usw gespeichert.

    class gObject {
      public:
        function SetPos(float X, float Y);
        function Draw();
    
      private:
        float PosX, PosY;
        Vertexlist* Vertex;
    };
    

    Dazu eine Funktion, die beispielsweise die bewegung berechnet.

    class mObject {
      public:
        function CalculatePos(gObject &obj);
        function Move(gObject &obj);
    
      private:
        [...]  S t u f f  [...]
    };
    

    Wenn nun Thread eins, gerade Zeichnen will und dafür auf die position zugreift, Thread zwei aber gerade die Position neu kalkuliert und einträgt, kommt es zu fehlern.
    Oder Ein Thread löcsht das Objekt, während ein anderer gerade versucht dessen Position neu zu kalkulieren.



  • makkurona schrieb:

    Back To Topic pls.. wobei sonen Flamewar hat auch was, wird aber glaube ich nicht gerne gesehen. (:

    Ach, das hier war höchstens eine Diskussion 😉 Aber hast recht, war Offtopic, tut mir auch Leid.

    Ich behandele die Fälle mal separat:

    makkurona schrieb:

    Wenn nun Thread eins, gerade Zeichnen will und dafür auf die position zugreift, Thread zwei aber gerade die Position neu kalkuliert und einträgt, kommt es zu fehlern.

    Da gibt's zwei sinnvolle Möglichkeiten: Entweder man schützt das ganze Objekt vor dem Zugriff, oder halt innerhalb der Klasse die Member. Hat halt beides seine Vor- und Nachteile. Wenn du das ganze Objekt schützt, hat der Benutzer dieser Klasse mehr Arbeit, beim Member-schützen läuft das Programm im Singlethread-Modus natürlich langsamer.
    Da muss man einfach abwägen, wie generell vs speziell die Klasse sein soll, ob andere Methoden auch geschützt werden müssen und in welchen Kontexten die Klasse benutzt wird.

    makkurona schrieb:

    Oder Ein Thread löcsht das Objekt, während ein anderer gerade versucht dessen Position neu zu kalkulieren.

    In diesem Fall muss definitiv der Verwalter des Objekts die Synchronisation übernehmen, etwa so:

    class ObjectManager
    {
        public:
            void deleteObject( string name )
            {
                Lock( prot );
                map::iter pos = objs.find( name );
                if (pos!=objs.end)  objs.delete(pos);
                Unlock( prot );
            }
    
            Object* queryObject( string name )
            {
                Lock( prot );
                map::iter pos = objs.find( name );
                if ( pos != objs.end )
                    return &*pos;
                else
                {
                    Unlock();
                    return NULL;
                }
            }
    
            void releaseObject()
            {
                Unlock( prot );
            }
    
        private:
            Mutex bzw CriticalSection prot;
            map<string,Object> objs;
    };
    

    Es gibt noch jede Menge anderer Möglichkeiten, query/release ist wahrscheinlich sogar eher suboptimal. Naja, es wird trotzdem hoffentlich deutlich, dass hier das Objekt selbst nicht synchronisieren kann, sondern dass der Verwalter dafür zuständig ist.

    Im Allgemeinen würde ich dir aber empfehlen, nicht irgendwelche generelle Regeln zu finden, sondern einfach loszulegen und zu sehen, welche Daten du in der Praxis so schützen musst. Im Zweifelsfall kannst du ja noch mal konkret nachfragen, aber ich bin der Meinung, dass sich beim direkten Arbeiten an Problemen viel "einfach ergibt" 🙂



  • ich gehöre nicht zu den leuten, die unnützen code schreiben. sowas würde ich nur machen, wenn es sinn machr und bei thread synchronisation brauche ich keine eigene klasse für...das ist übertrieben, dann kriegst du ein programmcode wo jede 2.funktion in eine eigene klasse gekapselt ist...vor sowas graut es mir.



  • makkurona schrieb:

    Wenn nun Thread eins, gerade Zeichnen will und dafür auf die position zugreift, Thread zwei aber gerade die Position neu kalkuliert und einträgt, kommt es zu fehlern.
    Oder Ein Thread löcsht das Objekt, während ein anderer gerade versucht dessen Position neu zu kalkulieren.

    Es gibt viele wege das problem zu loesen.
    -einer der vielleicht langsammsten waere wenn eines der threads den buffer solange fuer andere threads sperren wuerde bis er fertig mit der abarbeitung ist.
    -eine schnelle, aber nicht sonderlich speicherschonende version waere, wenn man mehrere buffer hat, (z.b. doublebuffering) und die resourcen wo man mit fuellen fertig ist, die buffer(pointer) tauscht.
    -eine weitere moeglichkeit ist, dass nur ein thread eine resource verwaltet. wenn ein anderer etwas damit anstellen will, muss er in ein command-buffer reinschreiben was der thread der die resource verwaltet damit machen soll(das geht oft komplett ohne critical sections)
    -eine weitere moeglichkeit ist wenn du die threads viel kleiner granuliert einsetzt. dass z.b. nicht zwei riesige threads existieren (z.b. einer fuer engine, einer fuer rendering), sondern dass es einen pool mit threads gibt und wenn mal etwas aufwendiges gemacht werden soll, wird das dem 'pool' mitgeteilt und der hetzt alle threads darauf (das geht mit z.b. openMP sehr einfach).
    -kein sync. es gibt resourcen da macht es nichts wenn sie 'kaputt' sind, weil sie garkeinte logic/programmlaufzeit beeinflussen. z.b. eine textur, du kannst sie mit dem 'mittelwert' fuellen und nutzen, waehrend ein anderer thread sie nach und nach laedt.


Anmelden zum Antworten