Boost Threads in Klassen. Gefährlich?



  • 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!



  • Ist evtl. noch eine zweite Methode mit einem anderen Mutex im Spiel? Produzierst Du evtl. einen Deadlock?



  • Tachyon schrieb:

    Ist evtl. noch eine zweite Methode mit einem anderen Mutex im Spiel? Produzierst Du evtl. einen Deadlock?

    Hab ich mir auch schon überlegt. Hab das mal erweitert:

    void DatenModul::anmeldenModul(Modul* M)
    {
      std::cout << "A" << std::endl;
      boost::lock_guard<boost::mutex> lock(ModulListeMutex);
      std::cout << "B" << std::endl;
      boost::thread::id ID = boost::this_thread::get_id();
      std::cout << "C " << ID << std::endl;
      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 einzelnen Module haben in ihrer (von oben bekannten) arbeiten()

    std::cout << Modulname << ", Thread: " << boost::this_thread::get_id() << std::endl;
    
      D->anmeldenModul(this);
    

    Die Ausgabe sieht so aus:

    main Thread: 0x91f030
    BeispielModul1, Thread: 0x91fb00
    A
    B
    C 0x91fb00
    BeispielModul2, Thread: 0x9205b0
    A
    BeispielModul3, Thread: 0x9210a0
    A
    

    Das Programm läuft mit voller CPU Last aber nix passiert!



  • Zeig mal die Definition von Datenmodul.



  • Ich zeig dir lieber gleich alles, aber bitte nicht schimpfen 😃

    http://codepad.org/vtje7q6m



  • Zwischen

    anmeldenModul
    abfragenModul
    eintragenDaten
    abfragenDaten
    

    und

    bereit
    

    gibt es ja nach Aufrufreihenfolge massig Potential für Deadlocks.



  • Vielen Dank für den Hinweis! So ganz klar ist mir aber noch nicht wo das sein soll.

    Ich hab eben zwei Verwaltungscontainer. Der eine speichert alle Module die es gibt, mit ID und Zeiger drauf, "ModuleListe" und der andere speichert welches Modul welche Daten erstellt (kann auch mehrfach sein!), mit DatenString zu ID, "DatenZuModulListe".
    Da die beiden erstmal zwei Container sind hab ich auch zwei Mutexe erstellt. DatenZuModulListeMutex und ModulListeMutex. Ich gebe zu dass ich in der Methode bereit() den ModulListeMutex auch zum synchronisierten Zugriff auf int bereiteModule verwende, aber das dürfte ja kein Problem sein.

    Meinst du das folgendes Szenario zu einem Problem führen kann: Man hat zwei Mutexe und zwei Methoden die beide Mutexe sperren, aber eben in umgekehrter Reihenfolge? Sodass der eine auf den anderen wartet?

    Ich hab nun mal den lock aus der Methode anmeldenModul() auskommentiert und siehe da es tut! Aber: Ich habe jetzt mal die main auskommentiert sodass ich nur EIN EINZIGES Modul erstelle und in dieser arbeiten() wird nix anderes gemacht als anmeldenModul(this). Trotzdem geht es (mit dem lock) nicht. Wie kann ein einziger Thread denn ein deadlock erzeugen?



  • Ach mein Gott bin ich ein Idiot!

    void DatenModul::anmeldenModul(Modul* M)
    {
      boost::thread::id ID = boost::this_thread::get_id();
      boost::lock_guard<boost::mutex> lock(ModulListeMutex);
      ModuleListe[ID]=M;
    
      #ifdef DEBUG
        std::stringstream ausgabe;
        ausgabe << "Datenmodul: " << abfragenModul(ID) << " hat sich angemeldet";
        debugging(ausgabe.str());
      #endif
    
    };
    
    std::string DatenModul::abfragenModul(boost::thread::id ID)
    {
      boost::lock_guard<boost::mutex> lock(ModulListeMutex);
      std::map< boost::thread::id, Modul* >::iterator i;
      i = ModuleListe.find(ID);
    
      if( i==ModuleListe.end() ) return "NICHT GEFUNDEN!";
      else return i->second->Modulname;
    
    };
    

    In der anmeldenModul() rufe ich zur Ausgabe gleich meine neuerschaffene abfragenModul() auf, welche auf den selben Mutex zugreift!



  • Mein Programm läuft inzwischen sehr gut und ich hab alles verstanden. Nun bin ich am Punkt, wo ich Objekte wieder vernichten sollte. Meine Objekte laufen jeweils in einem eigenen Thread, so wie es hier beschrieben wurde. Das ist meine main:

    while(true)
      {
        switch(Anwendungsfall)
        {
          case BEISPIEL:
          {
            TrajektorienplanerLRD1Modul* TR = new TrajektorienplanerLRD1Modul(D,"Trajektorienplaner");
            LaengsreglerD1Modul* LR = new LaengsreglerD1Modul(D,"Laengsregelung");
            CameraModul* CM = new CameraModul(D,"CameraModul");
    
            S->AnwendungsfallWechselSchranke->wait();
            Anwendungsfall = S->Anwendungsfall;
    
            delete TR;
            delete LR;
            delete CM;
    
            break;
          }
    ...
    
          case EINPARKEN:
          {
            EinparkplanerModul* EM = new EinparkplanerModul(D,"EinparkplanerModul");
            KIModul* KI = new KIModul(D,"KIModul");
    
            S->AnwendungsfallWechselSchranke->wait();
            Anwendungsfall = S->Anwendungsfall;
    
            delete EM;
            delete KI;
    
            break;
          }
        }
      }
    

    Nachdem die Threads gestartet sind wartet die main auf die AnwendungsfallWechselSchranke. Wird ein neuer Anwendungsfall gesetzt, werden die Module vernichtet und neue gestartet. Nun ist meine Befürchtung, dass die Threads aber weiterlaufen. Die Konstruktoren sehen z.B. so aus:

    KIModul::KIModul(DatenModul* d, std::string s)
    {
      boost::thread Faden = boost::thread(boost::bind(&KIModul::arbeiten, this));
    };
    

    Die arbeiten() hat eine while(true) und läuft somit ewig. Werden die Threads nun durch das delete der Zeiger vernichtet oder soll ich einen Destruktor schreiben? Ansonsten könnte ich noch die while beenden lassen.

    Vielen Dank schonmal!


Anmelden zum Antworten