Problem mit libboost Threads



  • Noch was anderes. Ist folgendes möglich?
    Ich übergebe der Threadfunktion, die eine Methode einer anderen Klasse ist,
    eine Referenz auf den Mutex.
    Bevor ich den Thread erstelle, sperre ich den Muxex mit mutex.lock().
    Die Threadfunktion beginnt mit mutex.lock() gefolgt von mutex.unlock(), dann
    die weiteren Anweisungen. Sie soll also erst einmal warten.
    Die Threadfunktion soll jetzt erst die weiteren Anweisungen ausführen, wenn
    im Mainthread der Mutex mittels mutex.unlock() freigegeben wird.
    Der Sinn ist, es soll der Thread erstellt werden, er soll aber trotzdem
    warten, bis noch ein paar set-Funktionen ausgeführt wurden, um ihn zu
    initialisieren.
    Es soll so auch möglich sein, den Thread an verschiedenen Stellen zu
    pausieren.



  • Hab's grad getestet. Funktioniert nicht. Die Threadfunktion läuft munter
    weiter, obwohl vor dem Erstellen des Threads der Mutex gesperrt wurde.
    Der Mutex wird im Mainthread erstellt und sofort gesperrt. Dem neu erstellten
    Thread übergebe ich den Mutex als Referenz. Der Thread beginnt mit einem
    lock(), gefolg von einem unlock(). Er sollte doch erst weiterlaufen, wenn
    der Mutex im Mainthread freigegeben wird. Ich will eigentlich nur den Thread
    schlafenlegen und wieder aufwecken können.
    Was kann das sein?



  • mase schrieb:

    Noch was anderes. Ist folgendes möglich?
    Ich übergebe der Threadfunktion, die eine Methode einer anderen Klasse ist,
    eine Referenz auf den Mutex.

    Ein Mutex steht immer in Beziehung zu einem Datum. Folglich sollte das Mutex ein Member derselben Klasse sein, die auch das zu schützende Datum enthält. Eine Übergabe des Mutex ist unüblich.

    mase schrieb:

    Bevor ich den Thread erstelle, sperre ich den Muxex mit mutex.lock().
    Die Threadfunktion beginnt mit mutex.lock() gefolgt von mutex.unlock(), dann
    die weiteren Anweisungen. Sie soll also erst einmal warten.
    Die Threadfunktion soll jetzt erst die weiteren Anweisungen ausführen, wenn
    im Mainthread der Mutex mittels mutex.unlock() freigegeben wird.
    Der Sinn ist, es soll der Thread erstellt werden, er soll aber trotzdem
    warten, bis noch ein paar set-Funktionen ausgeführt wurden, um ihn zu
    initialisieren.
    Es soll so auch möglich sein, den Thread an verschiedenen Stellen zu
    pausieren.

    .. dafür ein Mutex mit gegenseitigem Lock zu nutzen ist ebenso ungewöhnlich.

    Dafür nimmt man i.A. die Condition. Bei Dir vielleicht in Kombination mit einem einfachen Flag (bool), welches angibt, ob der Thread (Play) angehalten wird oder nicht (Member m_pause - s.Code unten).

    Zunächst einmal würde ich den Thread zum Member der Klasse machen, auf deren Daten der Thread zugreift. Ich unterstelle mal, dass im wesentlichen Informationen des 'cStream' benötigt werden. Also sollte der Thread Member von dieser Klasse sein; entweder direkt oder als Pointer.

    Das Play und Pause(mit der Möglichkeit zum Fortfahren) kann dann über eine einfache bool-Variable in Kombination mit der condition erreicht werden. Als Code-Skizze sähe das so aus:

    class cStream
    {
    public:
        typedef boost::mutex::scoped_lock lock_type;
    
        cStream() 
            : m_running( true )
            , m_pause( true )
            , m_condition()
            , m_mtx()
            , m_thrd( boost::bind( &cStream::Run, this ) ) // Bem.: warning wg. Zugriff auf 'this' ist ok, solange m_thrd letztes Element in cStream ist!
        {}
        ~cStream()
        {
            m_running = false; // beendet Schleife in Run-Methode
            Play(); // sicherstellen, dass der Thread nicht in der Pause fest hängt
            m_thrd.join(); // warte, bis Thread zu Ende ist
        }
    
        void Play()
        {
            lock_type lock( m_mtx );
            m_pause = false;
            m_condition.notify_one(); // weckt ggf. die wait-Methode
        }
        void Pause()
        {
            lock_type lock( m_mtx );
            m_pause = true;
        }
    
    private:
        void Run()  // hier rennt der Thread; Methoden-Ende ist auch Thread-Ende
        {
            for( ; m_running; )
            {
                check4pause();  // blockiert wenn 'm_pause == true' (s.u.)
                // .. Stream abspielen
            }
        }
        void check4pause()
        {
            lock_type lock( m_mtx );
            while( m_pause )
            {
                m_condition.wait( lock ); // blockiert bis 'notify'
            }
        }
    
        // --   Member
        bool m_running;
        bool m_pause;
        boost::condition_variable m_condition;
        boost::mutex m_mtx;
        boost::thread m_thrd;
    };
    

    Gruß
    Werner



  • Die Sache ist so:
    Die Klasse cStream bekommt vom Mainthread einen Pointer auf ein Objekt
    von cPlaylist. Den Mutex erzeuge ich im Mainthread, und übergebe ihn
    an cPlaylist und cStream. cStream::Play() läuft nun als Thread in einer
    Schleife. Dabei greift er sich jedesmal den zu spielenden Titel von
    cPlaylist ab. Im cPlaylist-Objekt kann sich aber während der Laufzeit
    die Playliste ändern. Um ein gleichzeitiges Lesen und Schreiben zu verhindern,
    wollte ich dazu den gleichen Mutex verwenden.
    Ich könnte mir zum Pausieren einen weiteren Mutex als Member von cStream
    anlegen, wie du gesagt hast.



  • .. Du kannst auch bei dem von mir beschriebenen Vorschlag den gleichen Mutex nutzen.
    Wie schon gesagt besteht zwischen einem Mutex und den zu schützenden Daten eine 1:1-Beziehung. In meiner Skizze wird nur 'bool m_pause' vom Mutex geschützt; es wäre aber kein Problem eine Playlist mit aufzunehmen.
    Man muss nur beachten, dass der Mutex innerhalb des cStream nicht zu lange gelockt bleibt. Das erreicht man z.B. in dem man in der Run-Methode unter Schutz des Mutex (gelockt) eine lokale Kopie eines Teils der Playlist anlegt und anschließend diesen ohne lock abspielt.

    Gruß
    Werner



  • Du meinst, dass der Stream in der Lockzeit nicht abreisst?
    Ich habe mir überlegt, das über einen vector von buffern zu machen (FIFO).
    Aber ist das in meinem Fall ok, den Mutex im Mainthread zu erzeugen und
    an beide Klassen zu übergeben?
    Mit dem separaten Mutex erreiche ich aber auch, dass ich während der Pause-
    phase die Playlist modifizieren kann. Das wäre mit einem gemeinsamen wohl
    nicht möglich.
    Aber deine Methode funktioniert tatsächlich.
    In meiner Boost-Doku steht gar nix von mutex::scoped_lock und notify_one().
    Danke dir!



  • mase schrieb:

    Du meinst, dass der Stream in der Lockzeit nicht abreisst?

    ich dachte eher daran, dass man von außen Pause() aufruft und der cStream mehr oder weniger unmittelbar reagiert, ohne erst das ganze aktuelle Lied abzuspielen - aus was für Einheiten besteht denn so eine 'PlayList' und wie lange dauert das Abspielen so einer Einheit?

    mase schrieb:

    Ich habe mir überlegt, das über einen vector von buffern zu machen (FIFO).
    Aber ist das in meinem Fall ok, den Mutex im Mainthread zu erzeugen und
    an beide Klassen zu übergeben?

    Kann ich so pauschal nicht sagen. Ein Mutex ist nach meinem Verständnis dafür nicht gemacht ...

    mase schrieb:

    Mit dem separaten Mutex erreiche ich aber auch, dass ich während der Pause-
    phase die Playlist modifizieren kann. Das wäre mit einem gemeinsamen wohl
    nicht möglich.

    Sicher - warum sollte das nicht möglich sein. Lasse Dich von dem Lock in 'check4pause' nicht beirren. Innerhalb von condition::wait wird der lock wieder geöffnet, sonst würde der Play ja auch nicht funktionieren.

    mase schrieb:

    Aber deine Methode funktioniert tatsächlich.
    In meiner Boost-Doku steht gar nix von mutex::scoped_lock und notify_one().

    guckst Du hier: mutex::scoped_lock & notify_one()

    Gruß
    Werner



  • Pause regiert direkt. Der Pausencheck wird bei jeden Füllen des Buffers
    ausgeführt. Und das sind 4kB Blöcke.



  • Jetzt habe ich ein neues Problem:
    Ich hab die Erzeugung des neuen Threads aus der Mainfunktion rausgenommen und
    in die Klasse cStream eingebaut.
    Das sieht jetzt so aus:

    Hauptprogramm

    int main()
    {
        cStream *Stream = new cStream(true);
    
        Stream->OpenStream();
        Stream->StartStream();
    
        boost::this_thread::sleep(boost::posix_time::seconds(5));
        Stream->PauseStream(true);
    
        delete Stream;
    }
    

    Klasse cStream

    void cStream::PauseStream(bool _Pause)
    {
        if (mPause == _Pause)
        {
            return;
        }
    
        if (!_Pause)
        {
            boost::mutex::scoped_lock Pause(mPauseMutex);
    
            mPause = false;
            mPauseCondition.notify_one();
    
            return;
        }
    
        boost::mutex::scoped_lock Pause(mPauseMutex);
    
        mPause = true;
    
        return;
    }
    
    int cStream::OpenStream()
    {
       //Hier wird die Serververbindung hergestellt
    }
    
    void cStream::StartStream()
    {
        boost::thread StreamThread(boost::bind(&cStream::Send, this));
    }
    
    void cStream::CheckPause()
    {
        boost::mutex::scoped_lock Pause(mPauseMutex);
    
        while (mPause)
        {
            mPauseCondition.wait(Pause);
        }
    }
    
    void cStream::Send()
    {
            while (!mStop)
        {
            //pause stream?
            CheckPause();
            //Jetzt werden weitere Funktionen zum Bufferfüllen und Abspielen
            //aufgerufen
        }
    }
    
    void cStream::StopStream()
    {
        mStop = true;
    
        PauseStream(false);
    
        return;
    }
    

    Alle Variablen, die mit m beginnen sind als Member in der Headerdatei
    deklariert.
    In der Funktion cStream::StopStream() musste ich zuerst die Pause aufheben,
    sonst stürzt das Programm an dieser Stelle ab.
    Mein Problem ist jetzt, dass wenn länger als ca. 10 Sekunden pausiert und dann
    die Pause aufgehoben wurde, sich das Programm ohne Fehler beendet.
    Es existiert auch kein Servertimeout.
    Ich kann mir nicht erklären, warum das so ist.



  • Ich hab das ganze durch den Debugger laufen lassen. Nach dem Aufheben der
    Pause wird der Buffer gefüllt und über libshout an den Server gesendet.
    Dann wird der Buffer noch einmal gefüllt. Wenn jetzt die Sendefunktion
    über libshout aufgerufen wird, erhalte ich folgende Meldung:

    Das Programm hat das Signal SIGPIPE (Broken pipe) empfangen.

    Das ganze immer beim zweiten Sendeblock. Und von libshout kommt keine
    Fehlermeldung zurück. Pausiere ich nur 5 Sekunden, läuft alles problemlos.



  • Hab die Ursache gefunden. Es ist tatsächlich ein Servertimeout, welcher sich
    aber wahrscheinlich nicht abfragen lässt. Ich baue jetzt nach dem Ende der
    Pause die Verbindung neu auf. Funktioniert ganz gut.


Anmelden zum Antworten