Boost Threads in Klassen. Gefährlich?



  • Hallo Leute!

    Wenn ich in Klassen boost-Threads einbaue und diese dann über eine Initialisierungsliste starte, dann sieht das ungefähr so aus:

    Klasse::Klasse(void) : thrd(boost::bind(&Klasse::Thread_Member, this)),
    {
    ...
    }
    

    Das muss anscheinend bei boost so gemacht werden, allerdings meckert der Kompiler das in der Initialisierung des Threads mit this gebunden wird:

    Warning	7	warning C4355: 'this' : used in base member initializer list
    

    Ist das Problematisch?

    Ich greife im Thread auf Member Funktinen der eigenen Klasse zu. Kann ich irgendwie sicher feststellen, wann die Klasse fertig erstellt ist?

    Wenn der Thread gestartet wird, und direkt auf eine Funktion der Klasse zugreift, kann es ja sein, dass die Klasse noch nicht fertig erstellt ist.

    Derzeit habe ich in jedem Thread ein Sleep(..) drin "um sicher zu gehen" das die Klasse fertig erstellt ist.

    Gruß
    WAR][FIRE



  • Das Problem ist ja nicht das "this" sondern das wo, nämlich in der Initialisierungsliste. Wenn du das this an nen Konstruktor übergibst, und dieser dann gleich klassenspezifische Sachen (ala Methodenaufrufe) machen will, geht das in die Hose, weil bei der Elementinitalisierung per Initialisierungsliste das this noch nocht den angenommenen Typ hat.
    Sollte aber im Konstruktor ähnlich sein.
    Trotzdem kannst du mal versuchen:

    Klasse::Klasse(void)
    {
         thrd = boost::thread(boost::bind(&Klasse::Thread_Member, this));
    }
    

    ob die Warnung dann weg geht.



  • Was KPL vorgeschlagen hat, funktioniert, seit boost.thread vollständig überarbeitet worden ist. Ich glaube ab Version 1.36 oder so.

    Die warning des Compilers ist berechtigt. Wenn der Thread startet greift er natürlich "sofort" auf 'alle' Member der Klasse zu. Man kann die warning ignorieren, wenn der Member thrd in der Klasse 'Klasse' der letzte Member in der Definition von 'Klasse' ist. Die Reihenfolge der Member in der Initialisierungsliste spielt keine Rolle!
    Außerdem sollte man von 'Klasse' auch nichts mehr ableiten, da dann aus dem Thread heraus über virtuelle Methoden auf Daten der abgeleiteten Klasse zugegriffen werden kann, bevor deren Konstruktor durchlaufen ist.

    Falls man diese Regeln einhält, kann man die warning ignorieren.

    WAR][FIRE schrieb:

    .. Derzeit habe ich in jedem Thread ein Sleep(..) drin "um sicher zu gehen" das die Klasse fertig erstellt ist.

    Das hilft in 99,5% der Fälle, wenn obige Regeln nicht eingehalten werden; ist aber nicht sicher. Und im anderen Fall ist es überflüssig, folglich rate ich davon ab.

    Gruß
    Werner



  • Werner Salomon schrieb:

    WAR][FIRE schrieb:

    .. Derzeit habe ich in jedem Thread ein Sleep(..) drin "um sicher zu gehen" das die Klasse fertig erstellt ist.

    Das hilft in 99,5% der Fälle, wenn obige Regeln nicht eingehalten werden; ist aber nicht sicher. Und im anderen Fall ist es überflüssig, folglich rate ich davon ab.

    ACK
    Ich würde es sogar einfach als "Pfusch" bezeichnen.

    Thread starten wenn alles fertig initialisiert ist. Wenn ältere Boost-Version oder Compiler mit denen das "Threads moven" nicht geht, dann einfach nen scoped_ptr<thread> verwenden.



  • Hallo Leute!

    Danke schonmal für die Tipps!

    KPL schrieb:

    Das Problem ist ja nicht das "this" sondern das wo, nämlich in der Initialisierungsliste. Wenn du das this an nen Konstruktor übergibst, und dieser dann gleich klassenspezifische Sachen (ala Methodenaufrufe) machen will, geht das in die Hose, weil bei der Elementinitalisierung per Initialisierungsliste das this noch nocht den angenommenen Typ hat.
    Sollte aber im Konstruktor ähnlich sein.
    Trotzdem kannst du mal versuchen:

    Klasse::Klasse(void)
    {
         thrd = boost::thread(boost::bind(&Klasse::Thread_Member, this));
    }
    

    ob die Warnung dann weg geht.

    Das funktioniert bei mir leider nicht, obwohl ich die aktuellste Boost Version benutze.

    Werner Salomon schrieb:

    Was KPL vorgeschlagen hat, funktioniert, seit boost.thread vollständig überarbeitet worden ist. Ich glaube ab Version 1.36 oder so.

    Die warning des Compilers ist berechtigt. Wenn der Thread startet greift er natürlich "sofort" auf 'alle' Member der Klasse zu. Man kann die warning ignorieren, wenn der Member thrd in der Klasse 'Klasse' der letzte Member in der Definition von 'Klasse' ist. Die Reihenfolge der Member in der Initialisierungsliste spielt keine Rolle!
    Außerdem sollte man von 'Klasse' auch nichts mehr ableiten, da dann aus dem Thread heraus über virtuelle Methoden auf Daten der abgeleiteten Klasse zugegriffen werden kann, bevor deren Konstruktor durchlaufen ist.

    Falls man diese Regeln einhält, kann man die warning ignorieren.

    WAR][FIRE schrieb:

    .. Derzeit habe ich in jedem Thread ein Sleep(..) drin "um sicher zu gehen" das die Klasse fertig erstellt ist.

    Das hilft in 99,5% der Fälle, wenn obige Regeln nicht eingehalten werden; ist aber nicht sicher. Und im anderen Fall ist es überflüssig, folglich rate ich davon ab.

    Gruß
    Werner

    So die Regeln habe ich jetzt alle eingehalten. Ich starte die Threads jetzt außerdem so, das ich in der main() nach erstellung des Objekts die Threads mit einer Variable starte:

    main()
    {
          Klasse Objekt;
          Objekt.Threads_starte = true;
    }
    

    In der Klasse selber warten die Threads jetzt auf die Freigabe

    while( Threads_starte!=true ) Sleep(1);
    

    Das mach das ganze schon deutlich sicherer. Allerdings finde ich die Lösung immernoch unschön...

    hustbaer schrieb:

    Werner Salomon schrieb:

    WAR][FIRE schrieb:

    .. Derzeit habe ich in jedem Thread ein Sleep(..) drin "um sicher zu gehen" das die Klasse fertig erstellt ist.

    Das hilft in 99,5% der Fälle, wenn obige Regeln nicht eingehalten werden; ist aber nicht sicher. Und im anderen Fall ist es überflüssig, folglich rate ich davon ab.

    ACK
    Ich würde es sogar einfach als "Pfusch" bezeichnen.

    Thread starten wenn alles fertig initialisiert ist. Wenn ältere Boost-Version oder Compiler mit denen das "Threads moven" nicht geht, dann einfach nen scoped_ptr<thread> verwenden.

    Das ist mir leider etwas zu hoch 🙂

    Wie weiß ich denn(in der Klasse selber) das die Klasse jetzt fertig ist?

    Was meinst du mit Threads_moven oder gar mit scoped_ptr<thread> ???

    Außerdem möchte ich irgendwie die Warnungen loswerden. (ohne diese zu deaktivieren 😉 )

    Gruß
    WAR][FIRE



  • WAR][FIRE schrieb:

    Das funktioniert bei mir leider nicht, obwohl ich die aktuellste Boost Version benutze.

    Was erhälst Du für eine Fehlermeldung und welche boost-Version benutzt Du?

    WAR][FIRE schrieb:

    Ich starte die Threads jetzt außerdem so, das ich in der main() nach erstellung des Objekts die Threads mit einer Variable starte:

    main()
    {
          Klasse Objekt;
          Objekt.Threads_starte = true;
    }
    

    In der Klasse selber warten die Threads jetzt auf die Freigabe

    while( Threads_starte!=true ) Sleep(1);
    

    Das mach das ganze schon deutlich sicherer. Allerdings finde ich die Lösung immernoch unschön...

    Ja - ich riet davon ab (s.o.), und hustbaer sprach von Pfusch. Lass es einfach weg, es sollte auch so funktionieren.

    WAR][FIRE schrieb:

    hustbaer schrieb:

    Ich würde es sogar einfach als "Pfusch" bezeichnen.

    Thread starten wenn alles fertig initialisiert ist. Wenn ältere Boost-Version oder Compiler mit denen das "Threads moven" nicht geht, dann einfach nen scoped_ptr<thread> verwenden.

    Das ist mir leider etwas zu hoch 🙂

    Wie weiß ich denn(in der Klasse selber) das die Klasse jetzt fertig ist?

    Was meinst du mit Threads_moven oder gar mit scoped_ptr<thread> ???

    'moven' ist die diesem Fall die einfache Zuweisung einer temporären Variable, wie bereits gezeigt. Im Unterschied z.B. zu einem int wird nicht der eine Thread auf den anderen kopiert, sondern lediglich von einer temporären Variable auf die andere Variable 'verschoben'.

    Dies geht z.B. nicht:

    boost::thread t;
        boost::thread t2 = boost::thread( &run );
        t = t2;
    

    (compiliert nicht)

    Gruß
    Werner



  • KPL schrieb:

    Das Problem ist ja nicht das "this" sondern das wo, nämlich in der Initialisierungsliste. Wenn du das this an nen Konstruktor übergibst, und dieser dann gleich klassenspezifische Sachen (ala Methodenaufrufe) machen will, geht das in die Hose, weil bei der Elementinitalisierung per Initialisierungsliste das this noch nocht den angenommenen Typ hat.
    Sollte aber im Konstruktor ähnlich sein.
    Trotzdem kannst du mal versuchen:

    Klasse::Klasse(void)
    {
         thrd = boost::thread(boost::bind(&Klasse::Thread_Member, this));
    }
    

    ob die Warnung dann weg geht.

    Ich habe mir nun eine Klasse "BeispielModul" gebaut es genau so umgesetzt wie du es beschrieben hast.

    BeispielModul::BeispielModul()
    {
      boost::thread Faden = boost::thread(boost::bind(&BeispielModul::arbeiten, this));
      Faden.join(); 
    };
    

    Von dieser habe ich wiederrum zwei Klassen abgeleitet, BeispielModul1 und BeispielModul2.

    In der main erstelle ich vier Objekte der Klassen und lasse mit boost::this_thread::get_id() in der Methode arbeiten() die IDs der Threads ausgeben:

    main()
    {
      BeispielModul M1;
      BeispielModul M2;
      BeispielModul1 B1;
      BeispielModul2 B2;
    }
    

    Das Ergebnis verwirrt mich:

    BeispielModul Thread: 0x1fc5060
    BeispielModul Thread: 0x1fc5320
    BeispielModul Thread: 0x1fc5320
    BeispielModul1 Thread: 0x1fc5320
    BeispielModul Thread: 0x1fc5320
    BeispielModul2 Thread: 0x1fc5320
    

    Wieso habe ich nun nur zwei unterschiedliche Threads? Und wieso wird BeispielModul 4 mal aufgerufen?



  • Weil immer nur ein Thread gleichzeitig aktiv ist. Wenn Du eine BeispielModul -Instanz anlegst, denn bleibt der Ctor so lange hängen, bis BeispielModul::arbeiten() fertig ist, weil Du nach dem Starten des Threads mit join synchronisierst. Und wenn BeispielModul1 und BeispielModul2 jeweils von BeispielModul abgeleitet sind: überlege mal, wieso BeispielModul 4 mal aufgerufen wird.



  • Der Konstruktor von BeispielModul wird jedesmal aufgerufen wenn eine abgeleitete Klasse instanziiert wird, logisch. Das ist auch weiter nicht schlimm weil BeispielModul eigentlich selber nie verwendet werden soll, sondern nur abgeleitete Klassen. Das heißt ich lass die arbeiten() einfach leer und alles ist in Ordnung.
    Ansonsten habe ich leider nicht verstanden was du mir sagen willst 😞
    Klar ist immer nur ein Thread aktiv, aber jeder hat doch eine eindeutige ID! Das auskommentieren des joins ändert auch nix.



  • Wenn ein Thread gestarted wird und dann wieder stirbt, dann wird die ID wieder frei. Sie kann für den nächsten Thread der gestartet wird wieder benutzt werden...



  • Tachyon schrieb:

    Wenn ein Thread gestarted wird und dann wieder stirbt, dann wird die ID wieder frei. Sie kann für den nächsten Thread der gestartet wird wieder benutzt werden...

    Achsooo! Ja logisch! So weit hab ich nicht gedacht 😃
    Aber nun musst du mir nochmal kurz auf die Sprünge helfen. Wann und nach welchen Kriterien erfolgen Thread-Wechsel? Ich dachte zumindest wenn ein sleep oder wait auftaucht.

    Ich hab nun in der arbeiten() verschiedene Dinge ausprobiert, aber nichts führt einen Thread-Wechsel herbei. Es wird immer nur ein Thread unendlich lange ausgeführt.

    void BeispielModul::arbeiten() 
    {
      std::cout << "BeispielModul Thread: " << boost::this_thread::get_id() << std::endl;
    
      while(true) ;
    
      wait();
    
      boost::this_thread::sleep(boost::posix_time::seconds(3)); 
    
    };
    


  • fabske schrieb:

    void BeispielModul::arbeiten() 
    {
      std::cout << "BeispielModul Thread: " << boost::this_thread::get_id() << std::endl;
    
      while(true) ; //Endlosschleife
    
      wait(); //was ist wait()? Wird nie erreicht wegen Endlosschleife
    
      boost::this_thread::sleep(boost::posix_time::seconds(3)); //wird nie erreicht
    
    };
    


  • Tachyon: Ich habe die Dinge einzeln ausprobiert. Bei der Endlosschleife passiert in der Tat nichts. Ich dachte vielleicht wird ein Thread-Wechsel bei jedem Durchlauf durchgeführt. Beim sleep terminieren die einzelnen arbeiten() wie vorherzusehen nach jeweils 3 Sekunden:

    main Thread: 0x1e9b030
    BeispielModul Thread: 0x1e9b1b0
    BeispielModul Thread: 0x1e9b4f0
    BeispielModul Thread: 0x1e9b4f0
    BeispielModul1 Thread: 0x1e9b4f0
    BeispielModul Thread: 0x1e9b4f0
    BeispielModul2 Thread: 0x1e9b4f0
    

    In den Beispielen im Internet wird meistens z.B. mit einem Puffer kommuniziert bei welchem man dann auf etwas wartet (Puffer ist leer). Gibt es eine Möglichkeit eine Endlosschleife zu definieren (in welcher etwas bearbeitet wird), aber so, dass am Ende jedes Durchlaufs ein Threadwechsel stattfindet (falls andere Threads ready sind)?

    EDIT: Die BeispielModul soll eigentlich nie instanziiert werden sondern dient nur der einfacheren Programmierung. Die arbeiten() der BeispielModul hab ich nun mal geleert und den Konstruktor entfernt. Auf einmal bekommen die beiden BeispielModul1 und BeispielModul2 unterschiedliche IDs!

    main Thread: 0x1339030
    BeispielModul1 Thread: 0x13391b0
    BeispielModul2 Thread: 0x13394f0
    


  • Eine Einführung in Boost zur Synchronisation von Threads:

    http://www.highscore.de/cpp/boost/multithreading.html#multithreading_synchronisation

    Gruß
    WAR][FIRE



  • WAR][FIRE schrieb:

    Eine Einführung in Boost zur Synchronisation von Threads:

    http://www.highscore.de/cpp/boost/multithreading.html#multithreading_synchronisation

    Gruß
    WAR][FIRE

    Genau mit dieser Doku arbeite ich! Da heißt es:

    Wenn Sie die Methode interrupt() aufrufen, wird der entsprechende Thread unterbrochen. Unterbrochen bedeutet, dass im Thread eine Ausnahme vom Typ boost::thread_interrupted geworfen wird. Dies geschieht aber nur dann, wenn der Thread einen Unterbrechungspunkt erreicht.

    Der alleinige Aufruf von interrupt() bewirkt nichts, wenn ein Thread keine Unterbrechungspunkte enthält. Beim Aufruf von interrupt() wird also nicht sofort eine Ausnahme vom Typ boost::thread_interrupted geworfen. Stattdessen überprüft der Thread in den bereits erwähnten Unterbrechungspunkten, ob interrupt() aufgerufen wurde - ist dies der Fall, wird eine Ausnahme vom Typ boost::thread_interrupted geworfen.

    Boost.Thread definiert eine Reihe von Unterbrechungspunkten. Ein Unterbrechungspunkt ist zum Beispiel sleep(). Da sleep() aufgrund der Schleife in thread() fünfmal aufgerufen wird, überprüft der Thread fünfmal, ob er unterbrochen werden soll. Zwischen den Aufrufen von sleep() kann der Thread im obigen Programm also nicht unterbrochen werden, und es wird keine Ausnahme vom Typ boost::thread_interrupted geworfen.

    Ich habe zwei entsprechenden arbeiten() erstellt:

    void BeispielModul1::arbeiten() 
    {
      std::cout << "BeispielModul1 Thread: " << boost::this_thread::get_id() << std::endl;
    
        for (int i = 0; i < 5; ++i) 
        { 
          sleep(1); 
          std::cout << i << std::endl; 
        } 
    
    };
    

    und

    void BeispielModul2::arbeiten() 
    {
      std::cout << "BeispielModul2 Thread: " << boost::this_thread::get_id() << std::endl;
    
        for (int i = 0; i < 5; ++i) 
        { 
          sleep(1); 
          std::cout << i << std::endl; 
        } 
    
    };
    

    Das Ergebnis:

    BeispielModul1 Thread: 0x9e41b0
    0
    1
    2
    3
    4
    BeispielModul2 Thread: 0x9e4450
    0
    1
    2
    3
    4
    

    Heißt das, dass ich die Threads manuell aus der main nach jeweils einer Sekunde unterbrechen muss, um dass sie sich abwechseln?



  • Hast Du evtl das join() wieder drin?



  • Tachyon schrieb:

    Hast Du evtl das join() wieder drin?

    Genau, das ist wieder drin! Warum darf es nicht rein?

    Ich hab es nun mal rausgemacht, aber die Sache wird dadurch nur noch schlimmer. Die Schleifen werden jetzt gar nicht mehr ausgeführt. Das Programm terminiert sofort mit dieser Ausgabe:

    main Thread: 0x1c4d030
    BeispielModul1 Thread: 0x1c4d1b0
    BeispielModul2 Thread: 0x1c4d4a0
    

    Hier mal mein Kode:

    #include "BeispielModul1.hpp"
    
    void BeispielModul1::arbeiten() 
    {
      std::cout << "BeispielModul1 Thread: " << boost::this_thread::get_id() << std::endl;
    
        for (int i = 0; i < 5; ++i) 
        { 
          sleep(1); 
          std::cout << i << std::endl; 
        } 
    
    };
    
    BeispielModul1::BeispielModul1()
    {
      boost::thread Faden = boost::thread(boost::bind(&BeispielModul1::arbeiten, this));
      //Faden.join(); 
    };
    
    #include "BeispielModul2.hpp"
    
    void BeispielModul2::arbeiten() 
    {
      std::cout << "BeispielModul2 Thread: " << boost::this_thread::get_id() << std::endl;
    
        for (int i = 0; i < 5; ++i) 
        { 
          sleep(1); 
          std::cout << i << std::endl; 
        } 
    
    };
    
    BeispielModul2::BeispielModul2()
    {
      boost::thread Faden = boost::thread(boost::bind(&BeispielModul2::arbeiten, this));
      //Faden.join(); 
    };
    
    #include <iostream>
    #include <time.h>
    #include <stdio.h>
    
    #include "Daten/Daten.hpp"
    #include "Daten/BeispielDaten.hpp"
    #include "Module/BeispielModul.hpp"
    #include "Module/BeispielModul2.hpp"
    #include "Module/BeispielModul1.hpp"
    
    int main()
    {
    
      BeispielModul1 B1;
      BeispielModul2 B2;
    
    return 0;
    
    }
    


  • Überlege mal, was join() macht, und wie lange die boost::thread Objekte leben, und wie lange die gesamte Applikation lebt. Nicht einfach nur fummeln, sondern mal überlegen, was Du willst.
    Die Threads laufen parallel zum Main-Thread. Der Main-Thread stirbt. Was passiert wohl mit Deinen anderen Threads?



  • Probiers in der main() mal mit folgendem Ende:

    //Windoof

    system("PAUSE");
    	return 0;
    

    oder

    //Linux

    std::cout << "Press Enter to continue....\n";
    std::cin.ignore(INT_MAX, '\n');
    std::cin.get();
    return 0;
    

    Damit wird der main Thread (also dein Programm) am Ende nicht beendet. Denn: Wenn deine main() abgearbeitet ist, dann schließt sich, wie man es kennt auch das Programm. Damit werden auch alle erstellten Threads gekillt. Vielleicht wirds mit der oben genannten Änderung etwas klarer.

    .join() wartet wie schon gesagt auf einen Thread bis er abgearbeitet ist.

    Also

    //Thread wird gestartet und die Memberfkt. "arbeiten()" läuft damit parralel zum main Thread.
    boost::thread Faden = boost::thread(boost::bind(&BeispielModul1::arbeiten, this)); 
    
    //Mit join blockiert der main Thread, da er ja auf den gerade gestarteten Thread wartet. 
    Faden.join();
    

    Damit hast du quasi einen ganz normalen Funktionsaufruf realisiert.

    Sprich: Der folgende Code ist vom Programmablauf her der selbe:

    BeispielModul1::BeispielModul1(void)
    {
    	//Aufruf der Memberfkt. "arbeiten". Auch hier wird gewartet, bis die
    	//Funktion abgearbeitet ist. 
    	arbeiten(); 
    	//Erst dann geht es im Konstruktor weiter.
    }
    

    Also nehme das join() weg, damit der main Thread parralel zum erstellten Thread "Faden" weiter laufen kann.

    Gruß
    WAR][FIRE



  • Ich arbeite unter Linux! 🙂
    Danke Tachyon und WAR][FIRE für eure Erklärung! Das war mir alles so noch gar nicht bewusst. Aber ist ja klar, sobald die main terminiert terminieren auch die Fäden, deshalb muss join raus und die main muss "unendlich" laufen, weil es ja auch nur ein Thread ist! Jetzt hab ich es verstanden!

    Ich hab mir nun ein Verwaltungsobjekt DatenModul für meine einzelnen Threads geschrieben, damit ich alle erreichen kann. Ich dachte mir die boost::thread::id sei dafür eigentlich perfekt geschaffen, wird sogar in der Doku zur Verwendung in Containern vorgeschlagen. Mein DatenModul hat deshalb eine map:

    std::map< boost::thread::id, Modul* > ModuleListe;
    

    Jedes meiner Modul (KlassenObjekte die jeweils in einem Thread laufen) rufen zu begin die Methode anmelden(this) auf:

    void DatenModul::anmeldenModul(Modul* M)
    {
      boost::lock_guard<boost::mutex> lock(ModulListeMutex);
      boost::thread::id ID = boost::this_thread::get_id();
      ModuleListe.insert( std::make_pair( ID, M ) );
      //ModuleListe[ID]=M;
    
      #ifdef DEBUG
        std::stringstream ausgabe;
        ausgabe << "Datenmodul: " << abfragenModul(ID) << " hat sich angemeldet";
        debugging(ausgabe.str());
      #endif
    
    };
    

    Die Sache kompiliert und funktioniert, aber sie terminiert nie! Egal ob ich ModuleListe[ID]=M oder die insert() aufrufe, sie wird nie fertig. Das Programm läuft einfach unendlich. Ich bin schon total ratlos und weiß nicht woran das liegen kann. Ohne Fehlermeldung weiß man gar nicht nach was man suchen muss!?

    Hat jemand von euch eine Idee? Vielen Dank schonmal!


Log in to reply