C++ und Zugriff auf Klassenvariablen von unterschiedlichen Threads aus?

  • Hallo,


    bislang dachte ich, dass alle Threads alles Variablen teilen und dass man deshalb den Zugriff auf selbige "regeln" müsste.


    Jetzt habe ich einen komischen Effekt, den ich mir nicht erklären kann:


    eine Klasse hat einen Vector als Member, also z.B. so:

    Code
    class Sample {
    ...
    private:
      std::vector<void *> pool;
      };


    Eine statische Methode der gleichen Klasse wird als Wrapper verwendet, um eine Memberfunktion als Threadfunktion auszuführen. Beim Zugriff aus dem Thread auf den Vector gibt es dann einen Absturz.
    Deshalb habe ich die unterschiedlichen Aufrufe mit Debug-Ausgaben vollgestopft und es sieht so aus:

    Code
    // vor dem starten des Threads
    if (!pool.empty()) fprintf(stderr, "pool has size of %lu\n", pool.size());
    fprintf(stderr, "starte thread ...\n");
    aThread->Start();


    Der Code in der Threadfunktion (der Memberfunktion) sieht ungefär so aus:

    Code
    fprintf(stderr, "check pool ...\n");
    if (!pool.empty()) {
       fprintf(stderr, "pool is not empty, so start work ...\n");
       }
    fprintf(stderr, "pool processed.\n");


    Programm-Ausgabe mit valgrind:

    Code
    starte thread ...
    check pool ...
    ==15751== Thread 2:
    ==15751== Invalid read of size 8
    ==15751==    at 0x40EE4A: std::vector<cThread*, std::allocator<cThread*> >::end() const (stl_vector.h:453)
    ==15751==    by 0x40EB13: std::vector<cThread*, std::allocator<cThread*> >::empty() const (stl_vector.h:575)


    Da ist Schluss mit meinem Latein. Der Aufruf von empty() vor dem Starten des Threads geht problemlos, der gleiche Aufruf aus dem Thread geht in die Hose? Muss ich da bei der Deklaration noch was bedenken/ändern?


    Ob mir wohl jemand eine Taschenlampe reichen, bzw. Licht ins Dunkel bringen könnte?


    Gruß Gero

    Ich bin verantwortlich für das, was ich schreibe, nicht für das, was Du verstehst!

    Einmal editiert, zuletzt von geronimo ()

  • Moin!


    Den Zugriff auf eine Variable von verschiedenen Threads aus musst du mit einer Mutex o.ä. schützen.
    Der vdr bietet in der Datei thread.h eine passende Klasse.


    Direkt bei "pool" definierst du zusätzlich "cMutex poolMutex;" und am Anfang deiner Zugriffsfunktion rufst du poolMutex.Lock() und am Ende (und vor jedem return) poolMutex.Unlock() auf.
    Oder du definierst am Anfang der Funktion

    Code
    cMutexLock lock(&poolMutex);
    lock.Lock();


    Dann wird durch den Destruktor von "lock" das Unlock der Mutex aufgerufen, ganz egal, wo die Funktion aussteigt.


    Damit ist sichergestellt, dass immer nur ein Thread zur Zeit auf pool zugreift.


    Lars.

  • Moin!


    Danke Dir für Deine Antwort.


    Der Punkt ist doch, dass ich noch garnicht soweit komme, irgendwas schützen zu können. Der Mutex-Mechanismus ist mir schon klar (denke ich :O )
    Wenn ich das Proggy einfach nur starte, ist schon gewährleistet, dass ab Threadstart nur der Thread auf die Variable zugreift. Das Hauptprogramm wartet einfach nur.


    Was ich nicht verstehe, ist der Absturz.
    Wieso ist der Zugriff aus einem anderen Thread ungültig?
    Was habe ich da nicht mitbekommen?


    Gruß Gero

    Ich bin verantwortlich für das, was ich schreibe, nicht für das, was Du verstehst!

  • Hallo Gero,


    ein bisschen mehr Code würde nicht schaden. Aber bist Du sicher, dass Du auf die gleiche (bzw. irgendeine) Instanz der Klasse zugreifst? Statische Methoden kannst Du auch ganz ohne Instanz aufrufen - dann passiert aber sicher was ganz Lustiges, wenn Du auf eine Klassenvariable zugreifst (falls das der Compiler überhaupt zulässt?).


    Gruß,


    eggi1973

  • Genau, wo wird instanziert und vor allen Dingen wann?


    Gerald


    HP Proliant MicroServer Gen8, Xeon E3-1230, 12 GB RAM, 3xWD red 2TB im RAID 5, 2xSundtek MediaTV Home DVB-C/T, L4M TWIN-C/T, Ubuntu Server 14.04.1, Plex Media Server
    Samsung UE55H6470

  • Moin!


    also das mit der statischen Methode hat wohl manche verwirrt. Die ist völlig Banane und nur dazu da, um einen Funktionsaufruf ohne Objekt-Instanz tätigen zu können.
    Ich habe das Prinzip von SDL übernommen - da wird ein Funktionszeiger und ein void Zeiger weiter gereicht. Von irgendwo her kann ich die Funktion über den Zeiger aufrufen und ihr den void-Zeiger-Parameter übergeben. In der statischen Funktion wird dann aus dem void-Zeiger die Objekt-Instanz und ich kann dann eine Memberfunktion aufrufen.


    Die statische Methode ist nur aus Gründen der Sichtbarkeit eine Memberfunktion - würde auch ohne gehen. Allerdings müsste die aufzurufenden Funktion dann public sein, was bei mir nicht der Fall ist.


    Aber der Reihe nach:
    Die Problemstelle stammt aus meinem Medienserver, der im Prinzip wie ein normaler HTTP-Server aufgebaut ist.


    In einem anderen Forum las ich, dass man STL-Container nur via Zeiger aus unterschiedlichen Threads ansprechen dürfe, weil sonst Copy-Construktoren beteiligt wären.
    Das halte ich für ein Gerücht, habe es aber trotzdem ausprobiert (d.h. der Container war vorher threads ohne *).
    Nach meinem Kenntnisstand werden bei einem Fork die Variablen kopiert, nicht aber bei einem Thread-Aufruf.


    Die Action-Methode läuft (derzeit noch) im Hauptprozess und sieht folgendermaßen aus:


    Es sieht also so aus, dass der Hauptprozess bis zur Zeile 11 von Action kommt und dort dann auf eine Verbindung wartet.
    Die Ausgaben von Zeile 4 und Zeile 6 kommen, d.h. ich gehe davon aus, dass der Zugriff auf den Vektor in Ordnung geht.
    In Zeile 7 wird der Thread, in dem dann der Absturz erfolgt, gestartet.


    Fehlt nur noch die statische Methode, in der dann die Memberfunktion aufgerufen wird:

    Code
    int cHTTPServer::cleanerThread(void *opaque, cThread *ThreadContext)
    {
      cHTTPServer *server = (cHTTPServer *)opaque;
    
    
      server->Cleanup(ThreadContext);
      return 0;
    }


    Bislang habe ich mich mit der Portierung des Servers beschäftigt. Da inzwischen das Wichtigste läuft, wollte ich mich den Feinheiten widmen, wie etwa dem Aufräumen während der Laufzeit. Pro Verbindungsversuch eines Clients wird ja ein neuer Thread gestartet. Dieser kann ein oder mehrere Anfragen bearbeiten und im Falle dass er ein Video streamt, auch sehr lange laufen. Pro Thread werden knapp 300 Byte verbraten und da hielt ich es für angebracht, etwas während der Laufzeit aufzuräumen.


    Der Absturz erfolgt in der Cleanup-Methode beim Aufruf von threads->empty()


    Vielleicht bin ich ja auch auf einem Holzweg - und Ihr könnt mir den richtige Weg, oder zumindest einen besseren Weg weisen.


    Gruß Gero

    Ich bin verantwortlich für das, was ich schreibe, nicht für das, was Du verstehst!

  • Moin!


    Jetzt fehlt noch die Stelle, wo cleaner erstellt wird und cleanerThreads aufgerufen wird. Evtl. ist der Parameter ja irgendwie "kaputt" und zeigt woanders hin als du denkst.
    threads muss kein Zeiger sein, es würde reichen, wenn du einen Zeiger übergibst.


    Falls du den kompletten Code noch nicht öffentlich zeigen willst, kannst du ihn mir auch per Mail schicken, dann schau ich einmal drüber.
    Es ist immer etwas schwierig, aus den Teilen herauszufinden, was da falsch läuft.


    Lars.

  • Hi Lars,


    hab herzlichen Dank für Deine (moralische) Unterstützung.
    Ich habe die Ecke erstmal mit #ifdefs geklammert um weiter machen zu können.


    Heut morgen habe ich auch wieder einen gordischen Knoten zerschlagen.
    Alle Unittests rund um den Aspekt liefen fehlerfrei im valgrind, aber wenn ich es als gesamtes laufen ließ hatte ich Zugriffsfehler, bzw. verlorenen Speicher ...
    Manchmal sieht man den Wald vor lauter Bäumen nicht.
    Wenn ich aber dann so einen Fehler finde, ist es meiner Stirne nicht gerade zuträglich ;)


    [OT]ich habe endlich kapiert, was C++ bedeutet: man braucht 10++ mal so viel Entwickleraufwand für ein Problem verglichen mit einer "modernen" Sprache ;) [/OT]


    Zitat

    Falls du den kompletten Code noch nicht öffentlich zeigen willst, ...


    Gut, das Backend ist nur die halbe Miete.
    Aber für sich betrachtet, ist die grobe Funktionalität gegeben, also könnte ich ein Projekt aufsetzen.
    Werde ich in der nächsten ruhigen Minute angehen (vielleicht am WE - die Woche sind viele Request vom second life auf dem Stack).


    Ack ja, aber besagte Stelle will ich Dir natürlich nicht vorenthalten:

    Code
    cHTTPServer::cHTTPServer(cServerConfig &Config)
     : config(Config)
     , cleaner(NULL)
    {
      threads = new std::vector<cThread *>();
      cleaner = new cThread(cleanerThread, NULL, "cleanup thread stubs");
    }

    Falls ich es noch nicht erwähnt habe - ich habe die Thread-Klasse vom vdr dahingehend erweitert, dass man eine beliebige Methode als Thread-Methode verwenden kann, ohne die Klasse ableiten zu müssen.



    Gruß Gero

    Ich bin verantwortlich für das, was ich schreibe, nicht für das, was Du verstehst!

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!