Boost Threads in Klassen. Gefährlich?



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



  • Ich würde Threads nicht einfach terminieren.

    Probiere Sie auslaufen zu lassen.

    z.B. klassenglobale, threadsichere, boolsche Variable "a"

    void thread(){
    
       while(a){
           //whatever...
       }
    }
    

    irgendwo außerhalb des Threads setzt du die Variable a auf "false" und der Thread beendet sich.

    Wenn du erst nach Beendigung des Threads in der main weiter arbeiten möchtest, könntest du das wieder mit .join(); realisieren.



  • Kann man die Threads eigentlich irgendwie sehen? Mit htop sehe ich ja Prozesse, aber keine Threads. Es wäre vielleicht ganz nützlich zu sehen wenn einer wirklich beendet wird.



  • Also in Visual Studio kann man sehen, was mit Threads passiert. Auch wenn sie beendet werden. Weiss nicht, wie das in anderen IDE's so ist.



  • Ich verwende einfach einen Editor und g++ unter Linux, also keine IDE. Sonst kann man das nicht irgendwie sehen?

    WAR][FIRE: so hab ich das nun auch gemacht! Jedes Modul hat eine bool laufen und eine Methode stoppen() welche diese auf false setzt.
    Das Problem ist nun nur, was ist wenn ein Thread gerade wait() aufgerufen hat und auf etwas wartet? Und zwar auf etwas was wegen dem stoppen() gar nicht mehr eintritt?



  • Da gibt es wieder funtktionen, die nach einer bestimmten wartezeit aufhören zu warten. Wenn nach einer bestimmten Zeit nichts gekommen ist, dann wird halt abgebrochen und entsprechens reagiert.

    Weiter könntest du dir eine Art Messagebaox Prinzip ausdenken. Sprich eine Variable setzen, in der geschrieben wird, dass ein Thread sich nun abmeldet.

    Sprich:

    void thread(){
    
    while(a){
    
    //whatever..
    }
    
    //Hier wird in eine Variable geschrieben, dass der Thread nun dicht ist.
    
    }
    

    Diese Variable musst du in dem Thread, der auf ein ereignis wartet natürlich abfragen.

    Das ist das was mir so jetzt einfällt. Für eine konkrete Antwort weiß ich zu wenig über das was du realisieren möchtest.

    Gruß
    WAR][FIRE


  • Administrator

    fabske schrieb:

    Ich verwende einfach einen Editor und g++ unter Linux, also keine IDE. Sonst kann man das nicht irgendwie sehen?

    Auf die Hardcore-Art geht es natürlich auch. Du musst dazu aber dein Programm mit Debugging-Symbols kompilieren, dass machst du mit der Option -g .
    Danach kannst du den gdb anschmeissen und während das Programm läuft mit zum Beispiel info threads die aktuell laufenden Threads anschauen.

    Infos zum GDB:
    http://www.gnu.org/software/gdb/

    Dokumentation GDB:
    http://sourceware.org/gdb/current/onlinedocs/gdb/

    Dokumentation GDB & Multithreading:
    http://sourceware.org/gdb/current/onlinedocs/gdb/Threads.html#Threads

    Grüssli


Anmelden zum Antworten