[C++] Thread safe

Online-Skater

Erfahrenes Mitglied
Hi Tutorianer,

habe mal eine Frage bzgl. Threadsynchronisation. Nachfolgend ein kleines Beispiel.
C++:
#include <iostream>

using namespace std;

class Test
{
  public:
    Test()
    {
        count++;
        cout << count << " Konstruktor" << endl;
    }
    ~Test()
    {
        cout << count << " Destruktor" << endl;
        count--;
    }
    static int count;
};

int Test::count = 0;

int give()
{
    Test t1;
    return 4;
}

int main()
{
    cout << "Start: " << endl;
    cout << give() << endl;
    cout << "Ende: " << endl;
    return 0;
}
Ausgabe:
Code:
Start:
1 Konstruktor
1 Destruktor
4
Ende:

Ich habe im Debugger den Verlauf beobachtet , beim zurückgeben des Wertes 4 (return 4) wird erst der Destruktor ausgeführt und danach der Rückgabewert übergeben. Das bedeutet das der return Befehl unterbrochen wird um Speicher aufzuräumen also aus dem sichtbarkeitsraum usw. ist das richtig ? Das bedeutet der return Befehl ist nicht threadsicher.

Beispiel mit boost::mutex
C++:
boost::mutex locker;
int Val;

void setVal(int v)
{
   boost::mutex::lock_guard(locker);
   Val = v;
}

int getVal()
{
   boost::mutex::lock_guard(locker);
   return Val;
}

Wäre dies threadsicher ? Zur Erläuterung die Klasse lock_guard hat nur einen Konstruktor der den Mutex locked und einen Destruktor der diesen wieder unlocked.

Alternative:
C++:
int getVal()
{
  int retVal;
  locker.lock()
  retVal = Val;
  locker.unlock();
  return retVal;
}
Hier ist ein wenig Mehraufwand da eine Kopie angelegt wird. Ist diese Methode ratsamer und warum ? Ich verstehe nicht ganz ob obiges Beispiel mit der Return-Unterbrechung Kompiler abhängig (Maschinenabh. oder Programmiersprachenabh. ist) Danke für Details :p
 
Also:

Wenn du die Klasse in der Funktion Deklarierst wird am Ende der Funktion automatisch der Destrukter aufgerufen und dann gehrt die Funktion zurück (return).

Das ist aber alles auch nicht weiter schlimm, denn wenn die Klasseninstance nur in der Funktion verfügbar ist (in Funktion deklariert) können andere Threads normalerweise nicht zugreifen.

Legst du die Klasse aber global an, wird bei Programmstart der Konstrukter und beim entladen des Programms der Destrukter aufgerufen.

Damit kann man in diesem Fall mit mehreren Threads sicher auf die Klasse zugreifen.

Wenn du es vollkommen Threadsicher haben willst schau dir mal CriticalSections an.

Gruß
Anfänger

Edit:
Nochmal kleine Ergänzung:
Natürlich wird vor dem Zurückkehren zur alten Funktion der Destrukter aufgerufen, da er ja sonst in der anderen Funktion aufgerufen werden müsste, was nicht möglich ist weil die Klasseninstance ja nur in der Funktion existiert. Hoffe das ist verständlich und auch richtig so. Zumindest habe ich das so verstanden.
 
Zuletzt bearbeitet:
Hallo "Anfänger92", danke für die Antwort, das es so ist ist mir auch klar und logisch. Die Frage ist eher ob die Anweisung return threadsicher ist oder ob da noch andere Dinge außer der Aufruf eines Destruktors eines lokalen Objektes passieren kann. Und ob die genannten Beispiele Threadsicher wären. :) Habe ein großes komplexes Programm welches ich threadsicher machen will und der Ansatz ist ja die Klassen threadsicher zu machen, d.h. Member welche man beschreiben und lesen kann müssen synchronisiert werden (kritische Bereiche). Habe jetzt nur ein Mutex für die ganze Klasse. Performanter wäre wahrscheinlich für jede Membervariable die gesetzt/gelesen wird einen Mutex zu haben doch scheint mir da der Overhead größer als der Nutzen. Wenn ihr dazu auch Literaturvorschläge habt, her damit ;)

mfg
 
Zuletzt bearbeitet:
Gehe nie davon aus, dass einzelne C/C++-Statements threadsicher sind. Der Code dahinter ist inzwischen relativ komplex, gerade wenn da noch eine Handvoll Objekte zerstört werden müssen.

Von "atomaren" Statements könnte man bestenfalls auf Assembler-Ebene ausgehen, und selbst da wäre ich sehr vorsichtig.
 
@Endurion Genau davon gehe ich ja eben nicht aus :p, demnach ist die alternative Variante siehe Post oben die threadsichere und der Vorschlag mit lock_guard eben nicht. Da der Destruktor aufgerufen wird bevor die Rückgabe stattfindet könnte ein Thread noch die Variable ändern (theoretisch). Finde das ziemlich uncool aber was soll man machen ;)
 
Da ja aber im return weder die Daten geschrieben noch gelesen werden ist es ja nicht so wahnsinnig tragisch wenn im return des einen Threads schon der Anfang des anderen Threads beginnt.
 
Um das gehts doch die ganze Zeit...Dass return Destruktoren asführt, die sehr wohl alles mögliche ändern können...
 
Nun ja, das Problem ist ja altbekannt, weshalb man ja auch grundsätzlich nur in Notfällen threadkritische Änderungen in den Konstruktor und den Destruktor legt. Das ist vom Prinzip her ähnlich wie bei der Übergabe der Parameter, wenn sie weder als Referenz noch als Zeiger übergeben werden, sondern als direkter Wert. Da wird der Konstruktor auch vor dem eigentlichen Schützen der Daten durch die Mutex aufgerufen, was bei mehr oder weniger unsauberem Programmieren dann Fehler verursacht.

Ergo wendet man einfach eine initialisierende und eine destruierende Funktion im Schutzbereich der Mutex an und verschiebt alle möglichen Änderungen (im Destruktor im Regelfalle alles) in diese Funktionen sodass ein Überlappen dieser Funktionen keine Probleme mehr verursacht. Zigfach in meinen Applikationen erflogreich getestet.
 
Zurück