Design-Frage: Threadsave Classes?
-
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.