QConcurrent oder QThread? Wie multithreaden in Qt 4.4.0?

Alles rund um die Programmierung mit Qt
Zonk
Beiträge: 51
Registriert: 17. September 2006 15:28
Kontaktdaten:

QConcurrent oder QThread? Wie multithreaden in Qt 4.4.0?

Beitrag von Zonk »

Hallo, ich habe folgendes Problem:

ich habe ein Programm ( Qt 4.4.0 ) mit einer GUI Klasse und einer Reihe von Klassen die das darunterliegende Fachkonzept bilden.

Das Fachkonzept ist über die Klasse "meta" hierarchisch aufgebaut und strukturiert..

Während des Programmstartes wird sowohl die GUI-Klasse als auch das Fachkonzept ( eigentlich nur die "meta"-Klasse, die wiederum instanziiert den Rest der Fachkonzeptklassen ) instanziiert.


Wenn ich nun in der GUI die Berechung starte, wird in der Fachkonzeptklasse eine Schleife gestartet, die alle abzuarbeitenden Objekte des gleichen Typs schrittweise durchrechnet.

Problem: Wenn ich große Anzahl von Objekten Rechnen lasse aktualisiert die GUI nur nach jeweils einem Schleifendurchgang, also nachdem er alle Objekte einmal einen Schritt weitergerechnet hat. Sind es zu viel Objekte, ist die GUI nur noch am freezen und unbenutzbar.

Lösungsidee:

Entweder die GUI in einen ausdrücklichen extra Thread packen, oder die umfangreiche Berechnung in einen extra Thread packen.

Konkrete Fragen:
1. Ist es sinnvoller die GUI in einen extra Thread zu packen, oder ist es einfacher die umfangreichen Berechnungen in einen Thread auszulagern?

2. Falls die Berechnungen ausgelagert werden sollen: Ist es sinnvoll die gesamte Workerschleife in einen Thread zu packen, oder alle Objekte innerhalb der Workerschleife ( ~ 250 ) in jeweils einen eigenen Thread zu packen? ( ist es hier evtl sinnvoll zu Poolen? wenn ja, wie funktioniert sowas? )

3. Realisiere ich das besser über QThread oder über QConcurrent?
Ansich soll ja Concurrent einfacher sein, aber ich finde die Beispiele in der Trolltech-Doku absolut nichtssagend.

Mein bisheriger Versuch mit QConcurrent ohne anderweitiges Threading in meine Anwendung funktioniert nicht (nicht kompilierbar, weiß aber nicht wie der QConcurrent::run( .. ) Aufruf lauten muss, da die Doku keine konkreten Angaben gibt. ), ist aber folgendermaßen realisiert:

void meta::workerfunc()
{
do
{
for ( ..i.. )
{
myObjett.calcStep();
}
}
while ( workerlooprequested == true )
}


void meta::parentfunktion()
{
....
QFuture<void> res = QtConcurrent::run( workerfunc );
.....

}


Hat jemand eine Idee wie ich das Problem lösen kann?

Mein Wunschzustand ist, das die Anwendung erkennt wieviele Kerne es hat, und die GUI in einem Kern abarbeitet und die workerToDo's auf den restlichen Kernen abarbeitet...


Ich hoffe sehr auf euch da ich nicht weiter weiß, vielen Dank!!


Gute Nacht und danke für eure Hilfe,

Torben
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

So weit ich weiß darf die Qt-Gui nur im Mainthread sein. Somit lagerst du deine Berechnung in Threads aus.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

1. Auch ohne die Beschraenkung, miestens bleibt der Maintrhead der guithread, und man erzeugt ein oder mehrere "frische" Threads fuer die "Jobs"
Ausnahmen gibts natuerlich ueberall :-)

2. Kommt ganz drauf an :-) auf ner maschine die MT nur schlecht oder rudimentaer unterstuetzt ist MT eh nur augenwischerei (aus sicht der performance) und erspart dem programmierer irgendwelche kniffe mit schleifen, um die GUI funktional zu halten ...

Auf ner Maschine wo MT gut unterstuetzt wird, die auch mehrere kerne hat, kommts ganz drauf an. Man muss immer der Balance finden zwischen aufwand fuer nen neuen thread (der eigentlich minimal ist) und den kosten fuers locking und dem vorteil den der eigene thread an sich bringt. was wieder vom system anbhaengig ist ...

bei 250 threads musst hoellisch aufpassen das die threads sich ned totlocken, und auch das es sich lohnt. wenn dein thread nur paar millisekunden laeuft bringt er rein gar nix ^^ Aber generell sind viele threads ned schlecht, obs sinnig ist is ne andere frage. Wenigstens skaliert das programm auf spaetern plattformen, wenn es gut gebaut ist ^^

bei heutigen anwendungen und mit nem standard compilern (gcc und VS2005) stellt sich die frage eigentlich gar ned.
um auf performance zu gehen sollt ma schon openmp oder aehnliches und prozessortechnische dinge betrachten.
QTHreads wuerd ich da nie verwenden (overhead, zu wenig kontrollmoeglichkeiten) ... also performance darf und soll da ned dein problem sein .... also programmier mit QThreads so, das es Sinn macht und Du wenig Verrenkungen fuer das threading allein machst. Denk mal ein workerthread iss in den meisten faellen ausreichend ....

Ist performance ein problem, bzw. soll spaeter eines sein, rat ich Dir das ganze in ne eigenne Uebersetzungseinheit zu packen (dll) ...
und dort dann mit compiler und bibs (openmp) zu experimentieren, und hoeherlevlige bibs eh rauslassen sondern nativ mit threads zu arbeiten ... und eventuell kommen dann auch mehrere plattformspezifische loesungen raus .... also ne optimierte dll fuer intel systeme, eines fuer amd chipsaetze und was generisches :-)

Mein Wunschzustand ist, das die Anwendung erkennt wieviele Kerne es hat, und die GUI in einem Kern abarbeitet und die workerToDo's auf den restlichen Kernen abarbeitet...
Die threads auf die kerne verteilt das BS, und das stellt sich mal so und mal so an ... ohne tiefere eingriffe in die threads wirst kaum ein nachvollziehbares verhalten bekommen. Weiss ned was die QT genau macht, oder wie die da eingreift, aber ausm bauchgefuehl raus wuerd ich sagen das mit der QT da ned wirklich weit kommst ...
Wie gesagt ist die frage wie wichtig die performance ist ... und MT wird meist ueberbewertet ... richtig performance holt man nur raus wenn ne ganze menge bedingungen stimmen ...

Noch was, steigst richtig in MT ein, und optimierst auf tieferer ebene, vergiss die QT dafuer ... CoW, oder impliciet sharing wie es trolltech nennt, und mt beissen sich ... da musst hoellisch aufpassen, um nachvollziehbare ergebnisse zu bekommen ... und verlierst ne menge performance, weil das cow expliciet aushebeln musst ...

Ciao ...
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Ich würde mich wohl mal mit den QtConcurrent - Framework beschäftigen. Das skaliert dann je nachdem wie viele CPUs drin sind und Du musst dich nicht drum kümmern.

RHBaum hat geschrieben:vergiss die QT dafuer ... CoW, oder impliciet sharing wie es trolltech nennt, und mt beissen sich ... da musst hoellisch aufpassen, um nachvollziehbare ergebnisse zu bekommen ... und verlierst ne menge performance, weil das cow expliciet aushebeln musst ...
http://doc.trolltech.com/4.4/threads.ht ... it-sharing
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Das Fachkonzept ist über die Klasse "meta" hierarchisch aufgebaut und strukturiert..
Wie hoch ist die wahrscheinlichkeit, das das "Fachkonzept" mal irgendwo anders auch verwendet wird ?
In sachen zukunftsicherheit, und wenn mal wer nach dir das project pflegen muss, wuerd ich strikt zwischen GUI und Logik trennen ....
kommt auch drauf an, wie "professionell" das sein soll, was da machst ...

Aber ich weiss, wir sind hier in nem QT Forum, das heisst du darfst soweiso nur die QT nehmen, ansonsten hasst hier nix zu suchen :-)

http://www.c-plusplus.de/forum/
z.b. ist nen besserer anlaufpunkt, um Frameworkneutralere Fragen zu stellen :-)

Ciao ...
Zonk
Beiträge: 51
Registriert: 17. September 2006 15:28
Kontaktdaten:

Beitrag von Zonk »

Vielen Dank für eure Antworten:

Generell habe ich mich mit MT schon beschäftigt, habe vor 1 Jahr mit der WindowsAPI MT programmiert. Die Standartprobleme von MT sowie ihre performancemässigen Vor- und Nachteile sind mir also geläufig.
Meine GUI und Fachkonzept sind relativ strikt getrennt und ja, eine kontinuierliche Weiterentwicklung ist geplant - es ist ein Uni-Projekt.

Das Problem ist nur, das die Forderung ach MT nachträglich aufgekommen ist, wo die Anwendung quasi schon fast fertig ist. Nun ist halt die Frage ob ich es noch schaffe die Rechenteile auszulagern, so das die GUI wieder vernünftig auf Interaktionen des Benutzers reagiert während das Programm rechnet.

Ich selber habe eine Quad-Xeon, in der Uni sind meinstens dual und Quad-cores unterwegs, es würde sich also lohnen..

QConcurrent klingt nach dem passenden Framework, nur bekomm ich es nicht ans laufen, und die Doku hilft nicht weiter: Die zeigt zwar wir ich mit QConcurrent::run( ) eine void Funktion _mit_ Argumenten starte, aber ich bekomme es zum Verrecken nicht hin, eine simple void-Funktion _ohne_ Argumente so zu starten.

Als Ausweichmöglichkeit bleiben mir dann halt die normalen QThreads, doch ich frage mich welche Funktion ich in die Run-funktion des Threads packen soll: Beim starten der Software werden ja alle Objekte des Fachkonzepts instanziiert aber noch nichts worker-mässiges gestartet, und beim erst beim Berechnungsstarten wird die Workerfunktion in dem meta-Objekt aufgerufen.

Ich kann ja Objekte nicht auf mehre Threads aufteilen, oder?

Muss ich also das gesamte Fachkonzept in einen anderen Thread packen? ansich habe ich damit keine Problem, nur was nehme ich dann als run funktion? den Konstruktor?

Wie müsste ich das machen um den Worker auf mehr als einen Thread aufzuteilen?


Sorry für die vielen dummen Fragen, bin eigentlich recht fit im Programmieren, habe nur den totalen Knoten im Hirn und komm nicht dahinter.

vielen Dank, Torben
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Naja wenn mit der Winapi scho viel gemacht hasst, sollte es ja mitm verstaendniss ned viel probleme geben ...

mit QConcurrent hab ich noch nix gemacht ... iss aber sicher auch geeignet.
Frage ob ich es noch schaffe die Rechenteile auszulagern, so das die GUI wieder vernünftig auf Interaktionen des Benutzers reagiert während das Programm rechnet.
Dafuer prauchst ned "viel haendisch" optimieren, die QT sollt fuer ausreichen ....
Ich kann ja Objekte nicht auf mehre Threads aufteilen, oder?
Objecte kann man nicht aufteilen :-) sondern eigentlich nur code/Laufzeit also threadtechnisch gesehen ....
Muss ich also das gesamte Fachkonzept in einen anderen Thread packen? ansich habe ich damit keine Problem, nur was nehme ich dann als run funktion? den Konstruktor?
Nein .... wie gesagt du teilst nur die Laufzeit auf ...

Irgendwo startet er doch den "berechnungsprozess" oder ? dafuer hat er doch ne methode die er aufruft .... und die dann eklig lange braucht, bis sie wieder zurueckkommt ....

genau diese methode kannst starten im run() ....

die run methode ist nix anderes als die threadproc in der winapi, nur das statt static und dem LPVoid parameter, das ganze schon auf die Objectinstanz gemappt wurde ...
Wie müsste ich das machen um den Worker auf mehr als einen Thread aufzuteilen?
Du muesstest die "arbeit" also den code in mehrere Haeppchen aufteilen, die parralel zueinander ausgefuehrt werden koennen ...
geht das nicht, oder nur sehr schwer, wirst mit mehr als einem workerthread eh ned gluecklich werden.

Lass erstmal die perofrmance beiseite, versuch erstmal nur die GUI zu entkoppeln ... und dein framework abzusichern ...

wenn dann noch zeit hasst, kannst an die optimierung denken :-)

Ciao ...
Zonk
Beiträge: 51
Registriert: 17. September 2006 15:28
Kontaktdaten:

Beitrag von Zonk »

Danke, das sind dohc schonmal ein paar Ansätze. Bin über das WE nicht da, aber am Montag werd ich mich mal ransetzen und sobald es was gibt ein Feedback geben, danke!

Viel Grüße, Torben
Zonk
Beiträge: 51
Registriert: 17. September 2006 15:28
Kontaktdaten:

Beitrag von Zonk »

OK, bin jetzt weiter. Kann jetzt in meiner Meta-Klasse Instanzen meiner Worker-klasse instanziieren und mittels start() ausführen lassen.

Ich nutze in meiner Software einen eigenen DebugMsg-Handler der die Meldungen in einem separaten Textedit ausgibt.

Nun habe ich das Problem dass meine qDebug(), qWarning() und qCitical()-Funktionen, die ich aus dem Workerthread heraus absetze, ins leere laufen und mit einem bösen Fehler im einer .asm-Datei von Qt ( assembler nehme ich an) einen Absturz verursachen.

Mittels Signals&Slots kann ich aber einen String an den GUI-Thread übertragen und der pinselt es normal und ohne Probleme in meine Debug-Fenster.

Frage: Ist es möglich das auch in dem Workerthread die Debug-Funktionen ganz normal laufen?

Vielen Dank, orben
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Ich nutze in meiner Software einen eigenen DebugMsg-Handler der die Meldungen in einem separaten Textedit ausgibt.
Textedit = GUI = Nur GUI Thread darf das ....
Mittels Signals&Slots kann ich aber einen String an den GUI-Thread übertragen und der pinselt es normal und ohne Probleme in meine Debug-Fenster.
Nicht wirklich performant, aber am ende der einfachere und unkompliziertere weg ....
DIe frage ist warum du das ned willst ....
Die signal / Slots laufen queued, also wird dein worker thread ned blockiert ...
Dein GUI Thread zu ausgelastet, dass er keine weiteren jobs verkraftet ?
Gehen Meldungen verloren ?

Ciao ...
Zonk
Beiträge: 51
Registriert: 17. September 2006 15:28
Kontaktdaten:

Beitrag von Zonk »

Hi, nein eigentlich geht es schon, das Programm ist nur ziehmlich groß (im relevanten teil einige 100 Debugmeldungen ) und es ist aufwändig alle DebugMeldungen etc rauszufischen.

Desweiteren kann man qDebug auhc dieverse Pointer unterschieben, die qDebug in der Ausgabe dann Typspezifiziert und den Pointer nennt. sowas habe ich bisher mit QString noch nicht geschafft.

Ich hab halt gedacht das Aufrufen von qDebug() / qWarning() etc. ist ansich erstmal eine nun-GUI Funktion, und erst die qDebug-Funktion intern macht die Verbindung zur GUI.


Wie ist das eigentlich mit einem Eventloop in dem Workerthread? Brauche ich dort einen eignen zzgl. dem Eventloop im Hauptprogramm??

Wie kann ich dort dann meine "Nutzlast" abrechnen lassen? Denn der workerThread hängt ja dann im "exec()" fest...


Vielen Dank für die geduldige Hilfe

Torben
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Also Normal sind QDebug und Co ned auf einen thread beschraenkt .... sie schreiben eigentlich auf die errorconsole, bzw in einen stream fuer nen externes debug programm, was auch von mehreren threads aus geht.
Problem ist was du in deinem DebugMsg-Handler machst ...
Der fuehrt natuerlich alles aus, in dem thread in dem auch das QDebug() aufgerufen wurde ....

Du kannst doch also im DebugMsg-Handler deine Slots/Signale einstezen an zentraler stelle.

Die eventloop brauchst du eigentlich nur, wenn du Signale aus anderen Threads empfangen willst und in deinen eigenen Thread ausfuehren, quasi queuen .... fuer die debug geschichte also ned, da das der empfaenger macht, in dem Falle der Gui Thread der ja ne eventloop hat von der QApplication ....

Nen thread in der Eventloop "aubzuschiesen" geht eigentlich auf 2 arten ...

- die brutale variante .... du killst (terminate) den thread von aussen. Musst aber aufpassen dass alles dann noch aufgeraeumt wird. sollte eigentlich nur in Notfaellen gemacht werden ....

- die nicely variante. Du schickst dem thread das quit() . Das setzt aber wiederum vorraus, das der thread zum abarbeiten seiner eventloop selber kommt. Funktioniert aquivalent zum GUI thread, den man auch ab und zu mal zum abarbeiten der loop kommt. Was wiederum eigentlich das zunichte macht was man erreichen will, wenn man die GUI lauffaehig halten will, und seine Jobs nicht zerteilen mag und deshalb multithreading verwendet.
Also meistens beenden die threads sich selber, in dem sie sich selber das close schicken, wenn sie fertig sind oder nen timeout von ner ressource bekommen oder aehnliches.
Eingreifen von aussen wird halt dann wie ein notfall (aussischt des threades) behandelt und macht nen richtiges terminate .... also wenn fehler im gui thread auftreten oder der Benutzer wirklich abbrechen will ...

Ciao ...
Zonk
Beiträge: 51
Registriert: 17. September 2006 15:28
Kontaktdaten:

Beitrag von Zonk »

Ja stimmt, den Debug-Handler könnte man sicher sowas beibringen. Nur wo genau liegt da der Fehler?

qDebug für den installierten Debughandler aus:

Code: Alles auswählen

#ifndef DEBUG_H
#define DEBUG_H

#include <QtDebug>
#include <QTextEdit>
#include <QMessageBox>
#include <QObject>


QTextEdit *deb;

void setDebugPointer(QTextEdit *deb_) {
	deb=deb_;
	deb->resize(500, 700);
}

void myMessageOutput(QtMsgType type, const char *msg) {

	if (deb==0)  {
		deb = new QTextEdit;
		deb->resize(500, 700);
	}
	deb->show();
	
	if (deb->isVisible()) {
		switch (type) {
			case QtDebugMsg:
				deb->append(QString("<b>Debug:</b> ")+QString(msg));
				break;
			case QtWarningMsg:
				deb->append(QString("<font color=\"blue\"><b>Warning:</b> ")+QString(msg)+QString("</font>"));
				break;
			case QtCriticalMsg:
				deb->append(QString("<font color=\"red\"><b>Critical:</b> ")+QString(msg)+QString("</font>"));
				break;
			case QtFatalMsg:
				QMessageBox::critical(0, "Debug - Fatal", msg);
		} //SwitchENDE
	}
}


#endif // DEBUG_H
in der main-funktion meines Programmes installiere ich mir den Handler folgendermaßen:

Code: Alles auswählen

#include <QtGui/QApplication>
#include "threadingtest.h"
#include "debug.h"

int main(int argc, char *argv[])
{
	QApplication a(argc, argv);

	//DEBUG-Handler installieren
	QTextEdit *debug = new QTextEdit;
	debug->hide();
	setDebugPointer( debug );
	qInstallMsgHandler( myMessageOutput );


	
	threadingtest w;
	w.show();
	return a.exec();
}

wo verstoße ich jetzt "gegen geltendes Recht"? *am Kopf kratz*

Viele Grüße vom ratlosen Torben
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Nun habe ich das Problem dass meine qDebug(), qWarning() und qCitical()-Funktionen, die ich aus dem Workerthread heraus absetze, ins leere laufen und mit einem bösen Fehler im einer .asm-Datei von Qt ( assembler nehme ich an) einen Absturz verursachen.
Da gibts (soweit ich das sehe) mehrere Probleme:
1. der Workerthread darf keine GUI-Operationen ausfuehren.. genau das macht er aber (qDebug() -> myMessageOutput() -> deb->show(); )
2. die Funktion "myMessageOutput" ist nicht thread-safe. Zwei threads koennen gleichzeitg ein qDebug() absetzen, aber der Code

Code: Alles auswählen

 if (deb==0)  {
      deb = new QTextEdit;
      deb->resize(500, 700);
   } 
hat Race-Conditions (zwei Threads koennen gleichzeitig da durch und koennten zweimal ein neues Textedit allokieren). Da fehlt dann also noch die Absicherung mittels Mutex.
Zonk
Beiträge: 51
Registriert: 17. September 2006 15:28
Kontaktdaten:

Beitrag von Zonk »

OK, Problem 2 Hab ich gelöst:

Code: Alles auswählen

	QMutex mutex;
	mutex.lock();
	if (deb==0)  {
		deb = new QTextEdit;
		deb->resize(500, 700);
		deb->show();
	}
	mutex.unlock();

Das Problem 1. Kapier ich auch langsam: Wenn man den Debughandler debuggt (*g*) dann motzt er beim Ankommen einer Message "qApp->thread()==QThread::currentThread()" - und will das als ASSERT absetzen - was ja wiederum den Debughandler involviert um das Assert-Fensterzu öffnen.
Das wiederum mag er nciht mit der Bemerkung das man Widgets nur im GUI-Thread erstellen darf und will wieder einen ASSERT absetzen - was wiederum erneut dne Debughandler für genau diesen Zweck aufruft und diese Schleife gibt halt sehr schnell einen Stack overflow.

Zur Fehlerbehebung:
Ich erstelle aber das Debug-Textedit nicht im Workerthread sondern anfans im GUIThread! Danach exisitert es ja und wird nicht neu erstellt.

Wo fällt er dann genau auf die Schnauze? beim append()? aber das ist doch eine Signal&Slot Verbindung, die müsste doch über Threads hinweg funktionieren oder?

Langsam wirds klarer, Torben :)
Antworten