Boost Threads in Klassen. Gefährlich?



  • 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



  • ich versuchs!



  • Ich will in diesem Thread mal wieder was zur ursprünglichen Frage posten. Bisher habe ich in all meinen Modulen im Konstruktor folgende Variante um das Modul als Thread zu starten:

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

    Jedes Modul ist von Modul abgeleitet und implementiert die virtuelle Funktion arbeiten().
    Nun möchte ich Module auch wieder stoppen können und verlagere das Starten des Threads in eine neue Funktion Modul::starteModul. Am besten wäre, wenn ich das gleich für jedes Modul gemeinsam schreiben könnte, da ja jedes diese Funktion erben wird.

    void Modul::starteModul(void)
    {
      Faden = boost::thread(boost::bind(arbeiten, this));
    };
    

    Wie gesagt, die arbeiten ist im Modul.hpp als virtual void arbeiten() = 0; deklariert und wird von den erbenden Modulen implementiert. Leiderklappt das nicht und ich würde gerne wissen warum. Was macht denn das "this"?

    Vielen Dank!



  • Ich mache das immer so aehnlich, wenn eine Methode in einem eigenen Thread ausgefuehrt werden soll (ganz ohne boost, aber leicht auf boost uebertragbar): http://www.linuxselfhelp.com/HOWTO/C++Programming-HOWTO-18.html



  • Die Reihenfolge, in der Objekte konstruiert werden, ist Basisklasse -> abgeleitete Klasse. Dein Basisklassenkonstruktor versucht also, eine virtuelle Funktion für etwas aufzurufen, das zu dem Zeitpunkt noch gar nicht existiert. (Link dazu)

    Das Schöne an boost::thread ist ja gerade, dass man nicht irgendwelche abstrakten Thread-Basisklassen benötigt, sondern ganz einfach jede Funktion einer Klasse (oder auch freie) in einem eigenen Thread laufen lassen kann. Also ab besten einfach den Thread da anlegen, wo er gebraucht wird (in der abgeleiteten Klasse).



  • Btw: Wie bei fast jeder Klasse, würde ich als Klassenmember für boost::thread einen boost::scoped_ptr nehmen.



  • Kóyaánasqatsi schrieb:

    Btw: Wie bei fast jeder Klasse, würde ich als Klassenmember für boost::thread einen boost::scoped_ptr nehmen.

    Warum? boost::thread ist movable...
    Simon


Anmelden zum Antworten