PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Mutexed Obj und Konstrukturaufruf


Gast
2008-03-03, 11:06:18
Hi,

wenn man ein Obj hat auf dem einige Funktionen implementiert ist, die für sich Thread-safe seien sollen

class Obj
{
public:
Obj( Obj& o);
Obj& operator=( Obj& o);

private:
int i
Mutex m;
}
Wenn es dann aber auch wieder nötig ist, zwei dieser Objekte einander zuzweisen.

Ob::Obj( Obj& o)
{
m.lock();
i = o.i;
m.unlock();
}
Greife ich auf i aber "unsicher" zu? D.h. ich müßte sowas, machen?

Ob::Obj( Obj& o)
{
m.lock();
o.m.lock();
i = o.i;
o.m.unlock();
m.unlock();
}
Besteht dabei aber nicht die Gefahr der Verklemmung? Insbesondere wenn diese Mutexe von verschiedenen Funktionen benutzt werden und mehrere dieser Objekte wechselseitig aufeinander zugreifen? Oder kann ich mir sicher sein, solange ich die locks und unlocks in rückwärtiger Reihenfolge wieder aufrufe, daß dies nie passiert - solange die Funktion aus anderen Gründen nicht im kritischen Bereich hängen bleibt?

Gibt es evtl. auch eine Möglichkeit ein Gruppe von Mutex erst zu lock'n, wenn sich atomar alle gleichzeitig lock'n lassen? Wie verhält es sich mit diesen MutexGuards, die in den Ctor/Dtors eines lokalen Stack-Objekts die Mutex lock'n/unlock'n? Gibt es eine definierte Reihenfolge, wie die Destruktoren beim Aufräumen des Stack aufgerufen werden - wenn man eben mehrere bräuchte?

danke

Ectoplasma
2008-03-03, 15:21:56
Sorry wenn ich das jetzt einmal so beantworte. Synchronisation in Konstruktoraufrufen ist der größte Murks.

Ein Objekt ist erst dann gültig, wenn der CTOR vollständig durchlaufen wurde. D.h., dass kein anderer Thread auf ein in der Entstehung befindliches Objekt zugreifen sollte. Erst nach der Erstellung dürfen andere Threads auf das Objekt zugreifen. Im Falle des Copy-CTORs sieht der Code so aus.


Ob::Obj( Obj& o)
:i(o.getI()) {
}

int getI() const {
m.lock();
int tmp = i;
m.unlock();
return tmp;
}


Die Mutexbenutzung kann man vereinfachen.


class AutoMutex {
public:
AutoMutex(Mutex _m) : m(_m) {
m.lock();
}

~AutoMutex() {
m.unlock();
}

private:

Mutex m;
}

// hier nochmal getI
int getI() const {
AutoMutex am(m);

return i;
}

Trap
2008-03-03, 15:33:15
Gibt es eine definierte Reihenfolge, wie die Destruktoren beim Aufräumen des Stack aufgerufen werden - wenn man eben mehrere bräuchte?
Gibt es: Immer umgekehrt der Konstruktionsreihenfolge. Innerhalb von Funktionen ist die Konstruktionsreihenfolge ja klar, bei Membern von Klassen ist sie nach der Reihenfolge der Definitionen im Quelltext.

Beim operator= hat man das Problem des möglichen Deadlock auf jeden Fall. a=b und parallel b=a...

Trap
2008-03-03, 15:37:05
Die Mutexbenutzung kann man vereinfachen.
Wenn man exceptions benutzt sind solche Ressourcehandles nicht nur Vereinfachung, sondern im Grunde Voraussetzung dafür, überhaupt verständlichen und korrekten Code schreiben zu können.

Ectoplasma
2008-03-03, 16:34:49
Wenn man exceptions benutzt sind solche Ressourcehandles nicht nur Vereinfachung, sondern im Grunde Voraussetzung dafür, überhaupt verständlichen und korrekten Code schreiben zu können.

Full ack.

Gast
2008-03-04, 08:09:01
Danke, diese Mutex Guards verwende ich ja auch schon. Heißt dies aber, daß ich bei Erstellung aufpassen muß, daß das Objekt nicht versucht wird, von 2 Threads gleichzeitig zu erstellen; oder da auf das Objekt sowieso erst danach zugegriffen werden kann, kommt es da auch zu keinen Problemen? Müßte mal überlegen, ob diese Situation "zwei Threads rennen um die Erstellung eines gemeinsamen Objekts" überhaupt sein kann; idR. sind die Daten ja bereits so weit, wenn die Threads mit der Arbeit beginnen.

Gibt es: Immer umgekehrt der Konstruktionsreihenfolge. Macht bei einem Stack ja eigentlich auch Sinn :)

Beim operator= hat man das Problem des möglichen Deadlock auf jeden Fall. a=b und parallel b=a... Stimmt, danke!

Ectoplasma
2008-03-04, 12:09:03
... oder das auf das Objekt sowieso erst danach zugegriffen werden kann, kommt es da auch zu keinen Problemen?


Ja, man sollte erst nach der Erstellung mit mehreren Threads darauf zugreifen. Zu Problemen kommt es im Normalfall nicht, da sowieso nur ein Thread ein Objekt erzeugen kann.

... Müßte mal überlegen, ob diese Situation "zwei Threads rennen um die Erstellung eines gemeinsamen Objekts" überhaupt sein kann;


Wie gesagt, es kann nur ein Thread ein Objekt erstellen.
Du könntest aber etwas bauen, dass zu konkurierenden Zugriffen während der Erstellung eines Objektes führen kann:



Thread 0:

Obj *globalPointer = 0;


Obj::Obj() {
i = 0;
globalPointer = this;
j = 1;
k = 3;
}

Thread 1:

void foo() {
int tmp1 = globalPointer->getJ();
int tmp2 = globalPointer->getK();
}


Solche Konstrukte wie oben sollte man natürlich vermeiden.

Der Deadlock beim operator= läßt sich z.B. dadurch vermeiden, dass man den Wert des anderen Objekts zunächst einer temporären Varibablen zuweist.
Das sieht dann so aus:



Obj &operator =(Obj &other) {
int tmp = other.getI();
setI(tmp);
return *this;
}


Wobei getI() und setI() natürlich synchronisiert sind.

eXistence
2008-03-04, 12:36:16
oh oh... den this-Pointer im Konstruktor rauszugeben ist aber gefährlich.

Denn wer weiß welche Zugriffe auf das Objekt stattfinden, während es noch nicht vollständig erzeugt ist...

Ectoplasma
2008-03-04, 12:55:32
oh oh... den this-Pointer im Konstruktor rauszugeben ist aber gefährlich.

Denn wer weiß welche Zugriffe auf das Objekt stattfinden, während es noch nicht vollständig erzeugt ist...

Öhm, wovon redest du? Der this Pointer wird im Zuweisungsoperator rausgegeben. Und das Beispiel oben mit der Zuweisung an eine globale Variable war als Beispiel gedacht, wie man es nicht machen sollte. Also lieber nochmal richtig lesen.

eXistence
2008-03-04, 13:02:13
Öhm, wovon redest du? Der this Pointer wird im Zuweisungsoperator rausgegeben. Und das Beispiel oben mit der Zuweisung an eine globale Variable war als Beispiel gedacht, wie man es nicht machen sollte. Also lieber nochmal richtig lesen.

oh verdammt, bin heute morgen wohl zu früh aufgestanden..., ich nehme alles zurück und behaupte das Gegenteil... ;)