QThread wann und wie locken ?

Alles rund um die Programmierung mit Qt
Antworten
bjoernt
Beiträge: 8
Registriert: 20. Februar 2008 12:36

QThread wann und wie locken ?

Beitrag von bjoernt »

Hallo,

ich beschäftige mich schon eine weile mit QT und habe eine Serveranwendung geschrieben welche auch im Grunde funktioniert.
Jedoch werde ich nicht ganz schlau wann ich was wo locken muss.
Und vorallem dem mit welchem Lock.

Ich habe mal ein Beispiel Code mitgegeben, der aktuell zeigt wo ich logge.
Nämlich im Object welches von meheren Threads modifiziert wird über die setter Methoden.

Fragen :

a) Könnt ihr euch das mal anschauen und mir Verbesserungsvorschläge schicken?

b) Kennt ihr ein gutes Buch über C++ und MultiThreadding?

c) Muss ich auch die privaten variablen eines Threads schützen ?
Bis jetzt habe es so verstanden, das ich nur ein mutex etc. benötige
wenn ich eine variable von mehreren Threads modifiziere.

d) Wenn ich von einer Klasse mehrere Threads anlege, sind die privaten variablen thread-safe ?



Hier mal mein Beispielcode



Definition Task :

Code: Alles auswählen

class Task {
  public:
    Task();

    //getters and setters    
    quint64 getID();
    void setID(quint64 _id);
       
    QString getStatus();
    void setStatus(QString _status);
    
  private:
    quint64 ID;
    QString status;
}
    
Implementierung Task :

Code: Alles auswählen

/*
 * Ctor Task object
 * Fill in initial default values
 */
Task::Task() {
  mutex = new QMutex();
  
  this->ID = 0;
}


quint64 Task::getID() {
  return this->ID;
}

void Task::setID(quint64 _id) {
  mutex->lock();
  this->ID = _id;
  mutex->unlock();
}




QString Task::getStatus() {
  return this->Status;
}

void Task::setStatus(QString _status) {
  mutex->lock();
  this->Status = _status;
  mutex->unlock();
}





Server Komponente :

Besteht aus mehreren Worker Threads und einem Dispatcher Thread (eigene Klassen).

Stellt darüber hinaus Klassen zum lesen von Configfiles, logging etc bereit.





Hier der Beispielablauf eines Arbeitsvorgangs:



Dispatcher-Thread #1:

a) Datenbank lesen


b) neues OPbject der Klasse Task erzeugen.
z.B.

Code: Alles auswählen

   Task taskObj = new Task();
   taskObj->setID(Datebankwert);

c) Schauen wie viele Worker Threads arbeiten und solange kein Thread frei ist, warten.


d) Wenn worker frei : Signal newTask absetzen mit pointer auf Task-Object




Server Komponete :

a) Task-Object klassifizieren und an geeigneten Worker-Thread vermitteln

b) newTask Signal wird per QueuedConnection zu einem freien Worker-Thread geschickt





Worker Thread #1:

a) Auftrag annehmen unter Verwendung des pointers auf taskObj:

Code: Alles auswählen

   taskObj = _task; //_task pointer to task object
   taskObj->setStatus("progress");
  
Signal progressTask absetzen.



Server Komponete :

a) Signal progressTask per QueuedConnection an den Dispatcher-Thread #1 weiterleiten


Dispatcher-Thread #1:

a) Status ermitteln

Code: Alles auswählen

   QString status = taskObj->getStatus();
  
b) Alten status aus der Datenbankk emitteln

Code: Alles auswählen

   oldStatus = query.value(0).toString();
   
c) Status updaten

Code: Alles auswählen

   if (status != oldStatus()) {
     query.exec("UPDATE ....");
   };
   
   
   

Worker Thread #1:

b) Auftrag abarbeiten

c) ggf Status korrigieren wie a)

d) Auftrag erledigt

c) Status auf "done" stellen unter Verwendung a)




Dispatcher-Thread #1:

Reihenfolge a), b), c) ermitteln

d) Auftrag intern als "done" markieren und taskObj löschen

Code: Alles auswählen

delete taskObj;
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Code: Alles auswählen

class Task {
  public:
    Task();

    //getters and setters   
    quint64 getID();
    void setID(quint64 _id);
       
    QString getStatus();
    void setStatus(QString _status);
   
  private:
    quint64 ID;
    QString status;
    QMutex * mutex; // das fehlte bei dir oder ? 
} 
kann man so machen, wenn man die Abhaengigkeit von QMutex ned in der Klassendeklaration haben will ... macht aber ned viel sinn wenn man eh QString bei hat ... entweder alles oder nix :-) und nen mutex passt super aufn stack, und hat eh die gleiche lebenszeit wie das beinhaltende Object ... also besser :

Code: Alles auswählen

class Task {
  public:
    Task();

    //getters and setters   
    quint64 getID();
    void setID(quint64 _id);
       
    QString getStatus();
    void setStatus(QString _status);
   
  private:
    quint64 ID;
    QString status;
    QMutex mutex; // als  normale Variable
} 

Code: Alles auswählen

Task::Task() {
  mutex = new QMutex();
 
  this->ID = 0;
}
von Initialisierunglisten haelts du nix oder ? :-)

Code: Alles auswählen

Task::Task():
mutex(new QMutex()),ID(0)
{

}
waere besser. this->ID ist bloedsinn, ID kann man direkt ansprechen.

Dein mutex ist nun eh ne normale Variable, also das ding wird aufn stack automatisch erzeugt, brauch ma nix machen ...

Code: Alles auswählen

Task::Task():
ID(0)
{

}
Waere nun dein richtiger Konstruktor.
und klar, ueberall in den methoden die zeiger deferenzierer fuer den mutex raus ...

Code: Alles auswählen

QString Task::getStatus() 
{
  return Status;
}
Sieht ungefaehrlich aus, isses aber nicht ganz ....

Regel: alles was nicht in einer atomaren operation erledigt werden kann muss geschuetzt werden, egal ob lese oder schreibzugriff !!!

das ding macht irgendwann ne copy von dem string, spaetestens bei der zuweisung an die andere variable, wenn returnwertoptimierung beim compiler eingeschalten ist ....
und nen string kopieren ist definitiv keine atomare operation ....

um das dng nu zu schuetzen, mit deiner methode, muesstest noch ne extra kopie anlegen (ned tragisch, QStrings sind ja cow ... aber ohje, das gibt noch andere probleme spaeter ) .

Code: Alles auswählen

QString Task::getStatus() 
{
  mutex.lock();
  QString strreturn = Status;
  mutex.unlock();
  return strreturn;
}
wenn dir deine stringzuweisung aus irgendwelchen gruenden fehlschlaegt, hasst du nen Problem. entweder schreibst zig zeieln fehlerbehandlungscode, oder du nutzt autoamtische locker objecte. Bei den automaischen objecten funktionieren einfache dinge auch wieder, weil keinen unlock irgendwo zwischenschieben musst. Besonders bei umfangreicheren code ist letztere die elegantere ... also besser :

Code: Alles auswählen

QString Task::getStatus() 
{
  QMutexLocker lock(&mutex);
  return Status;
}
Ciao ...
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Muss ich auch die privaten variablen eines Threads schützen ?
Bis jetzt habe es so verstanden, das ich nur ein mutex etc. benötige
wenn ich eine variable von mehreren Threads modifiziere.
Du musst natuerlich alles schuetzen, wo mehrere threads gleichzeitig !!! drauf koennen.
Wenn deine logic an anderer Stelle verhindert, das mehrere threads gleichzeitig!!! auf ne variable zugreifen, obwohl mehrere threads es nacheinander tun, dann brauchtest die ned schuetzen(locken) ! Aber wer ist sich schon sicher :-)

Also z.b. alle lokalen variablen in einer funktion brauchen ned geschuetzt werden ....
beim konstruktor was schuetzen ist auch unsinnig. erstellen tut ein object definitiv nur ein thread, und wenn nen anderer thread mit dem object "was macht" bevor der erzeugerthread mit dem konstruktor fertig ist, hasst eh nen maechtiges Problem :-)
Wennd er konstruktor auf eine referenz zugreift, die nen anderer thread grad bearbeiten koennte ... dann musst es aber schuetzen ...

Ciao ...
bjoernt
Beiträge: 8
Registriert: 20. Februar 2008 12:36

Beitrag von bjoernt »

Hallo,

das man bei den gettern auch locken soll ist mir klar.
Aber als ich den QMutexLocker verwendet habe, gabe es die
Situation das der setter gelockt hat weil er schreiben wollte und der
getter weil er lesen sollte. ==> Thread hat dich aufgehängt.

Ich habe dann versucht mit der Methode tryLock rum zu experimentieren bei dem getter aber was macht man mit dem Thread der lesen will, muss man den schlafen schicken oder kann ich da was mit QWaitcondition machen ?

Björn

P.S: Danke schon mal für die Replies
bjoernt
Beiträge: 8
Registriert: 20. Februar 2008 12:36

Beitrag von bjoernt »

Das lokale funktions-Variablen nicht geschützt werden müssen ist ja logisch.

Aber wie sieht es aus mit privaten Klassen variablen?
z.B:

ClassX hat eine status Variable

Ich erzeute nun 4 Threads von ClassX.
Innerhalb des Threads verwende ich die "status"-Variable.

Muss ich diese locken beim lesen/verändern wenn ich diese innerhalb des jeweiligen Threads benutze?

Oder kommt die status Variable 4 * vor sodass eine merhfach Benutzung der status-variable ausgeschlossen ist.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Aber wie sieht es aus mit privaten Klassen variablen?
Genau so wie mit anderen variablen auch ....

das heisst, wird eine instanz von einem object erzeugt, erzeugst du die membervariablen genau einmal auch ...

verwenden alle threads eine Instanz, verwenden die auch alle die gleichen membervariablen = du musst schuetzen.
Verwenden sie nur die selbe klasse, aber unterschiedliche Instanzen = jeder hat seine eigene kopie = du musst ned schuetzen ...
Ich erzeute nun 4 Threads von ClassX.
Das klingt total aua ^^ Threads und klassen haben nix miteinander zu schaffen ... eigentlich !!!
Aber ich denke das kommt von dem QThread mechanismus von dem du ableitest oder aggregierst. Zum lernen isses doof, wenn man gleich sowas vorgesetzt bekommt ....

normal, also unter winapi/Posix und C erzeugt man einen thread, diesem thread gibt man dort eine threadproc mit, was quasi die "main" von deinem neuem thread ist. wird er gestartet, arbeit er einfach diese funktion ab, das wars ...
also normal kennt er nix, ausser den lokalen variablen in der threadproc (unkritisch) + globalen variaben (kritisch, muessen natuerlich geschuetzt werden wenn die dann verwendet werden )
da man globale variablen ned nutzen soll ... ist der thread erstmal ziemlich einsam.
zusaetzlich wird nun beim erzeugen des threads eine referenz (in C isst das natuerlich ein zeiger, *void) mitgegeben, der dem neuen thread als parameter weitergereicht wird ...
in diesem zeiger kann so quasi alles drinne stehen .... und das ding ist das toor zur grossen welt, bzw der weg zu den daten des erzeugerthreads.
in c kann man also recht gut trennen, was gemeinsame und was lokale daten sind ... weil sie expliziet uebergeben werden.

was macht die QT ?

ned viel mehr ....

dein erzeugerthread erstellt ein neues QThread object, und erzeugt damit alle lokalen daten -> unkritisch.
im konstruktor von qthread glaub ich, wird der neue thread angelegt, und gleich auf paused gelegt.
die threadproc fuer den neuen thread ist eine statische memberfunktion an QThread. Als paramter bei der erzeugung hat die nen zeiger auf die instanz des QThreads uebergeben bekommen, in dem sie erzeugt wurde, das heist ihr parameter ist nen zeiger auf das QThread object, in dessem konstruktor es erzeugt wurde.
bisher alles unkritisch ...
mit start wird der thread losgeschickt ...
die threadproc macht nix anderes, als ihren paramater zu referenzieren, und daran die methode run() auszufuehren. das heisst der thread springt gleich in die run methode der Instanz ....

das wars ....

der erzeugerthread kennt zwar die instanz die er erzeugt hat, hat aber per default nix weiter mit dem ding zu tun als den konstruktor, und das start von aussen aufzurufen. zugriff auf lokale variablen hasst du nicht.
die threadproc bekommt zugriff auf die internen variablen da sie ja gleich in eine methode derinstanz geschickt wird, und die threadproc selber ist statisches member, koennt also auch, aber am ende ist das nur gut um die run methode protcted machen zu koennen, und die instanz wurde quasi fuer sie angelegt , das heisst das ding kann da drinne machen was es will .... wenn du nun von aussen nicht auf die lokalen daten der instanz zugreifst, gibts keine probleme ...

erzeugt der erzeugerthread nun ne neue Instanz von QThread, gibts auch nen neuen thread, der ueber die statische methode die neue instanz verpasst bekommt, er bekommt also seine eigene Instanz ....

Ergo du musst ned schuetzen, weil eine QThread instanz genau mit einem thread verkloppelt ist.
Natuerlich nur wenn du das verhalten ned grundlegend aenderst, und selber wilde dinge implementierst :-)

Ich hoffe das war einigermassen verstaendlich .... oder hab ich die Frage ned richtig verstanden ?

also wenn du 4 Instanzen einer klasse erzeugst, bekommst du auch 4 mal die Membervariablen.

Ciao ...
Antworten