[QT4] Generellen Frage zu QThread + QEventLoop

Alles rund um die Programmierung mit Qt
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

[QT4] Generellen Frage zu QThread + QEventLoop

Beitrag von kowi1134 »

Hallo!

Ich habe ein paar generelle Fragen zu QThreads und QEventLoops, da ich ein Programm mit mehreren Threads schreibe.

Dieses Programm hat 3 Knöpfe, die ein Starten/Fortsetzen, Pausieren und Stoppen eines Threads ermöglichen sollen.
  • Wenn ich "Start" drücke, rufe ich QThread::run() auf.
  • Wenn ich pausieren will, weiß ich nicht, wie ich z.B. mit QThread::wait() umgehen soll, da diese Funktion ja eine Zeit übernimmt. Zur Laufzeit weiß ich aber nicht, wie lange es dauert, bis wieder gestartet bzw. gestoppt wird.
  • Zum Stoppen habe ich gedacht, dass mir die Funktion QThread::quit() helfen kann, jetzt lese ich aber in der Doku zu QThread folgendes:
    QT Doku hat geschrieben:This function does nothing if the thread does not have an event loop.
    Nun, das Problem ist nicht nur, dass ich damit nichts anzufangen weiß und mir die Doku zu QEventLoop nicht hilft. Nein, das Problem ist, dass der Thread nicht einfach abgebaut werden darf, da in ihm dynamisch Speicher allokiert wurde und folglich vor einem Beenden dieser Speicher wieder freigegeben werden muss.
Ich wäre sehr sehr dankbar über Verbesserungen, Ideen und Fragen zu den 3 (bzw. 4) Funktionen, die über die Buttons realisiert werden sollen!!!
Und ich freue mich, dass diese QT-Gemeinde so viele nette Mitglieder hat, ganz im Ernst!

Ciao

Ergänzung:
Bisher habe ich zum Pausieren einfach eine boolsche Variable (pause) auf true gesetzt. An einer frequentiell aufgerufenden Stelle im Thread wurde der Thread dann so "eingeschhläfert":

Code: Alles auswählen

while( pause ) sleep(1);
Das ist zwar nicht die erste Wahl, aber es musste erstmal laufen.
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

Start: QThread::start()

Pause:
setPause(true);

Resume:
setPause(false);

Stop:
stop();

void MyThread::setPause(bool f) { m_fPause = f; if(!f) start();}
void MyThread::stop() { m_fStop= true;}


in run():


for (int i=m_schonErledigt; i < anzahlVonAktionen; i++)
{

if(m_fPause) {m_schonErledigt = i; return;}
if(m_fStop) {m_schonErledigt =0; return;}

}
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

lepsai hat geschrieben:Start: QThread::start()

Pause:
setPause(true);

Resume:
setPause(false);

Stop:
stop();

void MyThread::setPause(bool f) { m_fPause = f; if(!f) start();}
void MyThread::stop() { m_fStop= true;}


in run():


for (int i=m_schonErledigt; i < anzahlVonAktionen; i++)
{

if(m_fPause) {m_schonErledigt = i; return;}
if(m_fStop) {m_schonErledigt =0; return;}

}
Danke Lepsai!

Dennoch habe ich ein paar Fragen!

Muss ich mir gar keine Sorgen machen, wenn ich einen Thread pausiere, egal was für eine Schleife gerade im Thread selbst ausgeführt wird? Oder meinst Du, dass ich an einer besagten frequentiell aufgerufenden Stelle kontrollieren muss, ob es jetzt weitergeht oder nicht? Ich muss nämlich auf jeden Fall genau da wieder anfangen, wo ich aufgehört habe.
Ich denke, Du meinst, dass ich z.B. mit folgendem Code an der Stelle verharre, mit der es weitergehen soll:

Code: Alles auswählen

for( int i = 0; i < n; i++ )
{
    while( m_fPause )
       app.processEvents();

    // mache hier das, was zu tun ist.
}
Kannst Du mir erklären, was Du hiermit meinst?:

Code: Alles auswählen

for (int i=m_schonErledigt; i < anzahlVonAktionen; i++)
{

 if(m_fPause) {m_schonErledigt = i; return;}
 if(m_fStop) {m_schonErledigt =0; return;}

}
Ciao
Konrad
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

Wenn du wissen moechtest, an welcher Stelle die run() zu Ende war, z.B wegen setPause(), brauchst du den Zaehler m_schonErledigt. Im Konstruktor wird dieser mit 0 initialisiert...

qApp->processEvents() brauchste in dem Thread nicht...

Sonst sollte alles klar sein, oder?
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

Ok, jetzt wird es klarer.

D.h also ich soll den Thread komplett "von außen" pausieren, indem ich ihn stoppe, ja? Wobei eine boolsche Variable im Thread mir anzeigt, ob das letzte stoppen ein Stop oder eine Pause war. Ich schau mal, ob das für die thread implementierung nicht zu kompliziert wird. Denn stell Dir mal vor, dass Du einen rekursiven mergesort Aufruf wieder fortsetzen willst. Das scheint mir noch nicht besonders einfach, es sei denn mit m_schonErledigt grenze ich den Bereich ein, der schon sortiert ist. Alles Scheiße!!

Ciao
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

Zur Erklärung, was ich mit "innen pausieren" meine:

Alle meine Sortieralgorithmen sind Threads. Die Basisklasse aller Algorithmen enthält eine swap Funktion, die man ja bei jedem Sortieren braucht. In der swap() Implementierung wurde jedes mal geprüft, ob pausiert werden soll:

Code: Alles auswählen

void swap(int *a, int *b)
{
  int c = *a;
  *a = *b;
  *b = c;

  if ( stop )
    // baue eventuelles Hilfsfeld ab

  while ( pause )    
    if ( stop )
      // baue eventuelles Hilfsfeld ab
    else
      sleep(1);
}
So lief der Thread zwar immer weiter und konnte nur gestartet und gestoppt werden, aber er wurde an ziemlich genau der Stelle angehalten, an der er angehalten werden sollte. Ist das schlecht?
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

An lepsai:

Sorry, ich nerve nur ungern, aber könntest Du mir auf meinen letzten Beitrag oben antworten?

Ciao
Konrad
Goos
Beiträge: 360
Registriert: 17. August 2004 10:10
Wohnort: Black Forest

Beitrag von Goos »

kowi1134 hat geschrieben:aber er wurde an ziemlich genau der Stelle angehalten, an der er angehalten werden sollte. Ist das schlecht?
Was soll schlecht daran sein, wenn etwas genau so angehalten wird, wie es angehalten werden soll?
Ich komm mit der Formulierung irgendwie nicht richtig klar.

Goos
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

Die Sache ist doch die!

Zum Stoppen:

Ich verzichte in meiner Lösung auf die Standard Abbruchmethoden, die mir von Qt für meine Threads bereitgestellt werden.

Der Grund ist der, dass es viel zu aufwendig ist in jedem speziellen Thread ein ganz konkretes Wiedereinsteigen zu implementieren. Ich nutze nämlich Plugins und in diesem Zusammenhang auch Polymorphie.

Die Frage ist also die: Wie realisiere ich ein möglichst sauberes Stoppen (inkl. Aufräumen von Speicher) in einem QThread, der möglicherweise gerade in einer tief Verschachtelten Schleife oder Rekursion steckt?

Mein Vorschlag bei mehreren Methoden und Schleifen wäre ja:

Code: Alles auswählen

// Einstiegspunkt für Algorithmus
void MeinThread::MethodeInThread1()
{
    // Diese Variable kann von außerhalb des Threads manipuliert werden
    m_Stopped = false;

    MethodeInThread2();

    // Ich muss hier auf jeden Fall prüfen, ob m_Stopped wahr ist. Das ist
   //  zwar lästig, verhindert aber, dass ich jeden Algorithmus großartig mit
  // zusätzlichen Bedingungen versehen muss.
  if ( m_Stopped )
  {
     // Beende diese Methode auf adäquate Weise
     AufraeumenVonSpeicher();
     ...
  }
  
  // Mache weiter mit dem, was zu tun ist
  ...
}

void MeinThread::MethodeInThread2()
{
    for ( ... ; ... ; ... )
    {
        for( ... ; ... ; ... )
        {
            if ( stopped )
            {
                return; 
            }

            swap( &X, &Y );
        }
    }
}

void MeinThread::AufraeumenVonSpeicher()
{
    // z.B.
    delete [] m_HilfsFeld;
}
Ciao
Konrad
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

Der Grund ist der, dass es viel zu aufwendig ist in jedem speziellen Thread ein ganz konkretes Wiedereinsteigen zu implementieren. Ich nutze nämlich Plugins und in diesem Zusammenhang auch Polymorphie.
Das verstehe ich nicht. Wa haben Plug-ins und Polymorphismus mit Threads zu tun!?

Zweitens: Für mich ist ein Algorithmus eben ein Thread, da kann ich nur dann sauber abbrechen, wenn ich mir merke wie weit ich grade bin. Ob ein Algorithmus rekursiv oder nichtrekursive implmentiert ist, spielt überhaupt keine Rolle. Das ist nur ne technische Frage!
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

lepsai hat geschrieben: Das verstehe ich nicht. Wa haben Plug-ins und Polymorphismus mit Threads zu tun!?
Ok, vielleicht rede ich Mist, aber ich könnte schwören, dass ich mir dabei was gedacht habe. Es macht aber nichts, wenn Du und ich nicht (mehr) wissen was das war.
lepsai hat geschrieben: Zweitens: Für mich ist ein Algorithmus eben ein Thread, da kann ich nur dann sauber abbrechen, wenn ich mir merke wie weit ich grade bin. Ob ein Algorithmus rekursiv oder nichtrekursive implmentiert ist, spielt überhaupt keine Rolle. Das ist nur ne technische Frage!
Also, ich stelle mir das so vor:

Pausiert wird zentral an der Stelle, wo die swap() Funktion aufgerufen wird. Dagegen sollte eigentlich nichts sprechen.

Gestoppt wird der Thread nicht komplett durch äußeren Einfluss, sondern durch das setzten einer Variable namens m_Stopped auf true.
Der Algorithmus kann zu jeder Zeit diese Variable prüfen und dann seine Zelte manuell abbrechen.

In dieser Hinsicht legt das umgebene Programm sein Vertrauen in den Algorithmus, da dieser (und damit auch der Thread) sich selbst beenden muss. Damit vermeide ich z.B. dass ich mir merken muss wie weit an Algorithmus gearbeitet hat. Er soll nämlich nach einem echtem Stopp (und keiner Pause) wieder ganz von vorne anfangen.

Wird es jetzt klar, was mich beschäftigt?

Ciao
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

Mein Gehirn fängt an, zu schmelzen.... Sauberes Stoppen ist seblstverständlich ein Stoppen mittels m_Stopped = true, die Alternative heisst terminate() und kann mit dem Speicher nicht umgehen. Wenn du es so implementiert hast, also über m_Stopped = true, wo liegt denn dann dein Problem? Das verstehe ich nicht mehr...
In dieser Hinsicht legt das umgebene Programm sein Vertrauen in den Algorithmus, da dieser (und damit auch der Thread) sich selbst beenden muss
Das ist übrigens nicht so, da ich den Wert von Stopped von ausserhalb aendern kann. Sonst macht die Variable keinen Sinn. Dafür muss m_Stopped eine globale Variable sein, also keine Klassenvariable. Und durch ein Mutex geschuetzt.
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

Also, ich glaube, wir meinen mittlerweile das gleiche!
lepsai hat geschrieben: Das ist übrigens nicht so, da ich den Wert von Stopped von ausserhalb aendern kann. Sonst macht die Variable keinen Sinn. Dafür muss m_Stopped eine globale Variable sein, also keine Klassenvariable. Und durch ein Mutex geschuetzt.
Zunächst zur Konvention: Das "m_" vor einer Variable sollte andeuten, dass es sich um eine Variable vom Objekt handelt.

Ich möchte im Thread eine öffentliche Funktion stop() implementieren, die nichts anderes tut, als die Variable Thread::m_Stopped auf true zu setzten. Dafür brauche ich also schon mal keine globale Variable.
Was das Mutex betrifft, so reicht es doch, wenn ich in der Thread::stop() Methode folgendes schreibe, oder? (Vorausgesetzt, ich habe ein Thread::m_Mutex Objekt):

Code: Alles auswählen

{
    m_Mutex.lock();
    m_Stopped = true;
    m_Mutex.unlock();
}
Oder muss ich z.B. auch an den Stellen im Algorithmus ein Mutex benutzen, wo ich die Variable m_Stopped auslese?

Ciao und vielen Dank für Deine bisherige Hilfe. Ich denke, dass dies der letzte Punkt zu diesem Beitrag sein wird. Dann mache ich mich mal ans Implementieren und melde mich später wieder.

Konrad
lepsai
Beiträge: 573
Registriert: 14. September 2004 21:33
Wohnort: Berlin
Kontaktdaten:

Beitrag von lepsai »

Überall dort, wo du m_stopped ausliest, must du mit Mutex arbeiten...
kowi1134
Beiträge: 120
Registriert: 1. Mai 2005 17:48
Wohnort: Arnsberg

Beitrag von kowi1134 »

Hallöchen!

Es gibt noch eine Ungereimtheit!
lepsai hat geschrieben:Überall dort, wo du m_stopped ausliest, must du mit Mutex arbeiten...
Das schließt vermutlich mit ein, dass ich auch immer dann, wenn ich m_Stopped setzte, ein Mutex benutzen muss, stimmts?

Wenn ich in meinem Thread ein Feld über einen Zeiger auslese, dessen Inhalt auch außerhalb des Threads bearbeitet wird, dann muss ich vermutlich an beiden Stellen auch ein Mutex benutzen, oder? Dürfen das in diesem Fall auch unterschiedliche Mutexobjekte sein, oder darf ich für jede Variable nur ein Mutexobjekt benutzen?

Ciao
Konrad
Antworten