Netter Blog zum Thema QThread: You're doing it wrong

Alles rund um die Programmierung mit Qt
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Ihn stört es sowieso nicht, wenn die Anwendung wie gewünscht vom Bildschirm verschwindet, im Hintergrund jedoch zum sicheren Terminieren weiterläuft und schließlich selbst und korrekt beendet.
Seh ich nicht so !
Wenn dein Prozess Ressourcen belegt, die du gern wieder haben moechtest, Speicher oder gar nen File wo das fraglische Programm nen lock drauf hat ... isses sicher ned toll wenn das Ding weiter im hintergrund aktiv bleibt ^^
Und denk auch mal z.b. an die daemons. Wie werden die beendet, richtig mit kill. Es gibt unter posix verhaltensrichtlinien ... wenn Du nen SigKill geschickst bekommst, hasst ned endlos zeit deinen Prozess zu beenden.

Daraus gilt fuer deine 1. Aussage.
Man sollt schon schauen, dass nach so einer aktion der prozesses auch beendet und nicht endlos weitergefuehrt wird.
Also terminate darf ned die normale Art sein, wie man nen thread beendet. "Verboten" halt ich zu hart formuliert.

Ciao ...
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Sebastian Barth hat geschrieben:Ich wüsste nicht, wie QtConcurrent::run() uns da helfen könnte.
Ursprünglich ging es ja um den des "right way", um Code in einen anderen Thread zu packen. Seit Qt-4.4 gibt es QtConcurrent. In meinen Augen geht es damit in den meisten Fällen am einfachsten. Threads werden automatisch über den QThreadPool gemanaged. Einzig Slots werden nicht im neuen Thread-Context ausgeführt, sondern im Thread in dem das Objekt erzeugt wurde.

Wollte das auch nur der Vollständigkeit halber erwähnt haben, man braucht dafür eben nicht von QObject ableiten, nur für das "moveToThread()", man braucht nicht selber ein QThread-Objekt erstellen und sich um dessen Lebensdauer/Zerstörung kümmern, man muss auch nicht mal eine Klasse für die eine Methode schreiben, denn das geht auch mit einer freien Funktion.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Ich versuch grad durch die "moveToThread()" Gechischte durchzusteigen.
Irgendwie find ichs scho heftig, was da gemacht wird (intern).
Stellt sich fuer mich die Frage, warum erzeug ich nen Object mit ner Eventloop, um die eventloop zu beenden und dann im anderen thread neu zu starten ?
"Event processing will continue in the targetThread."
Was passiert da mit Events die noch in der schleife stehen ?
Wenn die "mitkopiert" werden, iss dann sichergestellt, das alle events auch im neuen thread laufen koennen ?
Denk ich da vielleicht zu kompliziert ?
Ok, ich bin eh ned so der QThread Programmierer (nehm die eher selten und in in einfachen Faellen).
Aber umsonst gibts das ja bestimmt nicht.
Hat wer mal nen Example, wie und warum man das benutzt und was man fuer designvorteile von bekommt ?

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

Beitrag von pfid »

Ich bin bisher mit dem Subclass-Ansatz recht gut gefahren. Wie schon angesprochen aus OOP-Gesichtspunkten vielleicht nicht so ideal, vermeidet in meinen Augen allerdings ein bisschen Stress, und ist mE auch intuitiver zu benutzen.

[edit] will sagen, dass ich auch noch nie moveToThread verwendet habe (Schaden über mein Haupt?)
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

RHBaum hat geschrieben:
Ihn stört es sowieso nicht, wenn die Anwendung wie gewünscht vom Bildschirm verschwindet, im Hintergrund jedoch zum sicheren Terminieren weiterläuft und schließlich selbst und korrekt beendet.
Seh ich nicht so ! [...]
Geht mir auch so...
RHBaum hat geschrieben:Ich versuch grad durch die "moveToThread()" Gechischte durchzusteigen.
[...]
Stellt sich fuer mich die Frage, warum erzeug ich nen Object mit ner Eventloop, um die eventloop zu beenden und dann im anderen thread neu zu starten ?
:?: ich glaube da hast du was falsch verstanden... Man erzeugt ein QObject, welches zu einer Eventloop gehört und verschiebt es dann zu einer anderen Eventloop (im Normalfall vom Eventloop der QCoreApplication zu einem eines QThreads).
RHBaum hat geschrieben: "Event processing will continue in the targetThread."
Was passiert da mit Events die noch in der schleife stehen ?
Wenn die "mitkopiert" werden, iss dann sichergestellt, das alle events auch im neuen thread laufen koennen ?
Keine Ahnung.. Das müsste ich auch im Code nachschauen bzw. ausprobieren.. in der Praxis entscheide ich mich gleich nach dem Erstellen eines neuen QObjects, zu welchem Threadkontext es gehört. Da gibt es noch keine "pending Events".. IMHO bin ich also noch nie in diese Situation gekommen..
RHBaum hat geschrieben: Hat wer mal nen Example, wie und warum man das benutzt und was man fuer designvorteile von bekommt ?
Ein QThread muss dann zu sich selbst verschoben werden ("moveToThread(this)"), wenn man die Slots dieser Klasse im Threadkontext ausführen möchte (typische Anwendung: QTimer in der run-Methode). Die andere (gemäss Blog "richtige") Variante ist die, dass man QThread nicht als konkrete Worker-Klasse behandelt sondern nur als "Kontext". Dann braucht es moveToThread(), um die Slots eines anderen QObjektes (der "Worker-Instanz") im Threadkontext auszuführen.
Wozu? Das ist schöneres OOP und IMHO auch etwas flexibler, weil z.B. der gleiche Kontext mehrmals verwendet werden kann oder man die Worker-Aufgabe auch im GUI-Kontext ausführen könnte (Trennung von "Aufgabe" und "Threadkontext").
pfid hat geschrieben: [...] Schaden über mein Haupt
Muss nicht sein... Wenn du (wie Barth Anfangs erwähnt hat) eine Worker-Instanz innerhalb der run-Methode erstellst oder überhaupt kein Eventloop benötigst (keine Signals und Slots, sondern "C-Style"-Synchronisierung) ist das ok .. :wink:

franzf hat geschrieben: Seit Qt-4.4 gibt es QtConcurrent. In meinen Augen geht es damit in den meisten Fällen am einfachsten. [...] Einzig Slots werden nicht im neuen Thread-Context ausgeführt, sondern im Thread in dem das Objekt erzeugt wurde.
QtConcurrent: da fehlt es mir an Praxis.. Wenn ich das Interface richtig verstehe, würde ich das z.B. nehmen, wenn eine Aufgabe optimiert an den Rechner (Anzahl Cores) parallelisiert werden müsste.. Beispiele: in einer Graphik-Anwendung irgendwelche Bildmanipulationen auf ein ganzes Verzeichnis (alle Bilder z.B. verkleinern) ausführen oder mehrere Berechnungen parallel auf Rekorderdaten durchlaufen, usw...
Aber ist es auch das richtige Werkzeug, wenn man z.B. eine periodische "Datenerfassung" (z.B. ein VNC-Client, eine Kameraanbindung, eine Messdatenerfassung oder sonst was in diese Richtung) realisieren muss? Sprechen hier nicht gerade die Sache mit den Slots und das Cancel-Management dagegen?
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

solarix hat geschrieben:
pfid hat geschrieben: [...] Schaden über mein Haupt
Muss nicht sein... Wenn du (wie Barth Anfangs erwähnt hat) eine Worker-Instanz innerhalb der run-Methode erstellst oder überhaupt kein Eventloop benötigst (keine Signals und Slots, sondern "C-Style"-Synchronisierung) ist das ok .. :wink:
Bisher hatte ich den Ansatz, die Threadableitung selbst als Worker zu behandeln. Eventloop hab ich innerhalb des Threads bisher noch nicht gebraucht. Aber auch hier auf Arbeit kommt der gleiche Ansatz zum Einsatz, allerdings ohne Qt und mit eigener pthreads Implementierung (hier allerdings weitaus komplexer).
RHBaum hat geschrieben:Und denk auch mal z.b. an die daemons. Wie werden die beendet, richtig mit kill. Es gibt unter posix verhaltensrichtlinien ... wenn Du nen SigKill geschickst bekommst, hasst ned endlos zeit deinen Prozess zu beenden.
sigterm ;)
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

solarix hat geschrieben:QtConcurrent: da fehlt es mir an Praxis.. Wenn ich das Interface richtig verstehe, würde ich das z.B. nehmen, wenn eine Aufgabe optimiert an den Rechner (Anzahl Cores) parallelisiert werden müsste..
Vllt. bin ich da auch zu unbedarft. Aber QtConcurrent::run() seh ich als die eigentlich normalste Form, eine Funktion in einem anderen Thread laufen zu lassen (eben wie boost::thread), mit dem zusätzlichen Feature, dass sich Qt um die Threads kümmert, evtl. wartet bis ein Thread aus dem Pool zur Verfügung steht - meintest du das mit den Cores?
Sprechen hier nicht gerade die Sache mit den Slots und das Cancel-Management dagegen?
Hab ich oben schon geschrieben: SLOTS werden im Thread abgearbeitet, in dem das Objekt liegt. Willst du SLOTS in einem anderen Thread haben, musst du wohl moveToThread() oder von QThread ableiten. Und am Cancelmanagement ändert sich mMn. nix, du hast wenn du willst ein Objekt, welches du mit Funktionsaufrufen Stilllegen kannst, nur halt nicht über SIGNAL/SLOT.
Wie gesagt: ich empfinde das als die leichteste Art schnell mal eine Funktion in einem anderen Thread laufen zu lassen. Ich spar mir das Ableiten von QObject/QThread, und brauch auch keinen eigenen QThread mitschleppen, das macht Qt für mich.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

SLOTS werden im Thread abgearbeitet, in dem das Objekt liegt.
Definier das mal genauer!
IMHO:
Slots werden in dem Threadcontext ausgefuehrt, in dessen context die verabreitende eventloop laeuft.
jedes exec() startet eine Eventloop.
Der Construktor von QObject merkt sich die ThreadID und damit nen verweiss auf die zugehoerige eventloop.
bei nem connect wird das ding also immer in der Eventloop des threads gehangen, in dessen context der Construktor des Empfaengers (aus dem connect aufruf) aufgerufen wurde ...

Das wirklich imens wichtig fuer das generelle verstaendniss ....

jetztz kommen die fragen:
Welche Vorteile, oder Gründe habe ich davon, ein QObject in einem Thread zu erzeugen, dann in einen anderen thread umzuhaengen, um "es" laufen zu lassen.

Also eine Aufgabe im vornhinein als QObject modellieren, einem thread zuweissen, abarbeiten lassen ... und das ganze ausschliesslich ueber Signale steuern.

Das Erfordert ja, das die Aufgabe klar und abarbeitungsfertig ist, bevor ich nen thread zur verfuegung habe .... oder ?

Den fall hab ich eher seltener, meist krieg ich die aufgabe und hab threads sofort zur verfuegung, bzw erzug mir sofort welche (windows sei dank).
Bei nem threadpool stellt sich die Frage, was knall ich in die queue ... die aufgabe scho fertig als QObject modelliert, oder irgendwas abstractes, woraus ich schnell die aufgabe generieren kann (hat man meist sowieso nen mechanismuss fuer, fertige aufgaben als QObject bekommt man ja ned irgendwoher) ...

So klassen fuer Jobs hatt ich scho haeufiger, nur hab ich die nie mit der qt modelliert. mit einer einheitlichen do() methode gehen die halt auch sher komfortabel in nen threadpool aubzuarbeiten ... weiss ned ob das Modellieren als QOBject oder adaptieren im vorhinein mir irgendeinen Vorteil bringen wuerd .... da hab ich noch nen Brett vorm kopf, scheinbar :-)


Ciao ...
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

franzf hat geschrieben:....
Vllt. bin ich da [...] meintest du das mit den Cores?
Ich versuche mich mal an einem Beispiel:
Aufgabe1: eine Software muss 100 Bilder möglichst schnell verkleinern. Das Verkleinern (QPixmap::scaled()) selbst muss nicht abbrechbar sein, wohl aber der ganze Auftrag (z.B. Abbruch nach 30 Bildern). Möglichst schnell bedeutet, dass die Anzahl paralleler Aufträge mit den CPU-Cores übereinstimmt, denn wenn man 100 Bilder auf einem Quad-Core parallel konvertiert dauert das länger, als wenn immer 4 Aufträge in Arbeit sind (IO-Zeit wird durch den "Ressourcen-Streit" verlängert und es gibt viel mehr Thread-Kontext-Wechsel).
Lösung: QtConcurrent, weil da Qt auf einem Quad-Core vier Threads wählt und auf einem Dual-Core zwei.. was jeweils die optimalste Lösung ist ohne dass ich mich als Entwickler um die Anzahl CPUs kümmern muss. Ein "scale" muss nicht abgebrochen werden, aber wenn der User auf "Abbruch" klickt, kann man über das Cancel-Management alle verbleibenden Aufträge verwerfen.

Aufgabe2: Eine Software soll von zwei USB-Kameras die Bilder so schnell wie möglich liefern. Der Bildfluss soll vom Anwender jederzeit ein/ausgeschaltet werden können.
Lösung: Zwei QThreads (abgeleitet oder mit zwei Kontroller-Klassen), weil die Anzahl Threads nicht von der CPU abhängt und man diese Aufgaben steuern muss (Signals/Slots, Flags/ Mutex.. irgendwie halt).

Stimmt das so?
RHBaum hat geschrieben: Definier das mal genauer!
hmm.. ein paar Beispiele:

man hat einen QPushButton, einen QThread (nicht abgeleitet) und möchte auf Klick einen Slot in einer "Workerklasse" ausführen:

Vorbereitungen im Kontext des MainThreads (GUI):

Code: Alles auswählen

  QPushButton *btn = findCh....
  QThread *context = new QThread();
  Worker   *worker = new Worker();
Nun die connect/moveToThread-Varianten:

Code: Alles auswählen

  connect(btn, SIGNAL(clicked()), worker, SLOT(doWork()));
Resultat: eine DirectConnection, welche doWork im GUI-Kontext ausführt, weil "btn" und "worker" zur gleichen Eventloop gehören.

Code: Alles auswählen

  worker->moveToThread(context);
  connect(btn, SIGNAL(clicked()), worker, SLOT(doWork()));
Resultat: eine QueuedConnection, welche doWork im Thread-Kontext ausführt weil "btn" und "worker" zu unterschiedlichen Threads gehören.

Code: Alles auswählen

  connect(btn, SIGNAL(clicked()), worker, SLOT(doWork()));
  worker->moveToThread(context);
Resultat: eine DirectConnection, welche doWork im GUI-Kontext ausführt, weil die Connection als "DirectConnection" gewählt wurde (die beiden QObjecte gehören zum Zeitpunkt von connect() zum gleichen Eventloop).

Code: Alles auswählen

  connect(btn, SIGNAL(clicked()), worker, SLOT(doWork()), Qt::QueuedConnection);
  worker->moveToThread(context);
Resultat: eine QueuedConnection, welche doWork im Thread-Kontext ausführt, weil die GUI-Eventloop das clicked()-Signal brav an den Thread weiterreicht.

Code: Alles auswählen

  worker->moveToThread(context);
  connect(btn, SIGNAL(clicked()), worker, SLOT(doWork()), Qt::DirectConnection);
Resultat: eine DirectConnection, welche doWork im GUI-Kontext ausführt, weil die GUI-Eventloop den Aufruf des Slots gleich selbst vornehmen muss..

ich hoffe das stimmt so 8)

[EDIT1]
RHBaum hat geschrieben: Den fall hab ich eher seltener, meist krieg ich die aufgabe und hab threads sofort zur verfuegung, bzw erzug mir sofort welche (windows sei dank).
Das verstehe ich nicht so recht, denn QThread-sei-dank hat man das auf jeder Plattform jederzeit..

[EDIT2]
Habs ausprobiert: bei Variante 3 (moveToThread nach AutoConnection) lag ich falsch.. der Slot wird auch hier im Thread-Kontext ausgeführt... Eigentlich logisch, denn bei der AutoConnection wird ja erst beim konkreten Signal entschieden.. hübsch :wink:
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

solarix hat geschrieben:Stimmt das so?
(fragt der Guru den Noob :))
Ja, jein, ... kommt drauf an :D
QtConcurrent::run() nimmt sich wie gesagt die Threads aus dem (globalen) QThreadPool. Die Anzahl der Threads steht in QThreadPool::maxThreadCount, welches per default QThread::idealThreadCount() ist.
Von daher: Wenn du die Funktionalität, die im Thread ausgeführt werden soll, selber implementierst, kannst du hier natürlich gleich über QObject/Qthread-Subclassing gehen. Wenn die Funktionalität aber bereits (z.B. in einer Library) vorliegt, empfände ich es als Overhead, da nochmal nen QObject/QThread-Wrapper schreiben zu müssen (für Kamera auslesen gibt es mWn. schon fertige Libs). Und in Qt ist glaub ich QtConcurrent::run() die einzige Möglichkeit, eine fertige (Member)Funktion asynchron laufen zu lassen. Um jetzt sicher zu gehen, dass auch alles parallel läuft, und nicht die Anzahl CPUs mein Programm bremst, kann ich (temporär) QThreadPool::maxThreadCount() erhöhen - gibt ja nen setter.

Da QtConcurrent::run() die einzige Methode im namespace ist, die ihre Anzahl Threads aus dem ThreadPool zu nehmen scheint (nirgendwo sonst stand was darüber), nehme ich an dass die mapped/mappedReduced/... IMMER auf QThread::idealThreadCount() laufen werden! Wenn ich falsch liege bitte schimpfen. Somit sollte es sicher sein, die Threadzahl im ThreadPool zu erhöhen, ohne dass einem die anderen QtConcurrent-Sachen dazwischenfunken...
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

franzf hat geschrieben: [...]
Und in Qt ist glaub ich QtConcurrent::run() die einzige Möglichkeit, eine fertige (Member)Funktion asynchron laufen zu lassen. Um jetzt sicher zu gehen, dass auch alles parallel läuft, und nicht die Anzahl CPUs mein Programm bremst, kann ich (temporär) QThreadPool::maxThreadCount() erhöhen - gibt ja nen setter.
[...]
Evt. liege auch ich falsch, aber ich finde dass sich das irgendwie mit
http://doc.trolltech.com/4.6/threads-qtconcurrent.html hat geschrieben:Programs written with QtConcurrent automatically adjust the number of threads used according to the number of processor cores available. This means that applications written today will continue to scale when deployed on multi-core systems in the future.
beisst. Wenn dieses Verhalten nicht zu der Problemstellung passt ist es IMHO der falsche Weg.. In Qt gibt es halt mittlerweile viele Lösungsstrategien. Ich habe diese für mich so in etwa wie folgt geordnet:
- QtConcurrent, wenn viele ähnliche Funktionsaufrufe (z.B. Berechnungen) optimal parallelisiert werden sollen.
- QThreadPool/QRunnable wenn die Anzahl Threads dynamisch ist (die Threads " kommen und gehen" dauernd) wie z.B. KI-Bots in einem Spiel oder so
- klassischer QThread wenn ein spezifisches Problem (z.B. eine Maschinensteuerung) aus dem Hauptthread ausgelagert werden soll.

Weiters finde ich enorm wichtig, dass eine MT-Software vollständig auch als solche entworfen wird. Denn wenn du einfach einen X-beliebigen Funktionsaufruf (einer Library) in einen Thread wirfst (mit QtConcurrent weil's bequem ist..) und die Entwickler haben da non-reentrant-Funktionen verwendet (ctime, gethostbyname &CO) dann wird einem das Debugging noch viel Freude bereiten..
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Ich hab jetzt grad mit dem wordcount-example gespielt, und neben den mappedReduced eine andere Funktion mit run() laufen lassen. Und auf meinem Singlecore wird tatsächlich erst nachdem die eine Funktion fertig ist die andere gestartet! Erhöhe ich den maxThreadCount des QThreadPools, laufen die Sachen parallel.
Da ist die Doku aber sehr inkonsequent, dass das mit dem Management über QThreadPool NUR bei QtConcurrent::run() dabei steht, ansonsten nirgends Erwähnung findet - sowas irritiert mich immer :(

Eine weitere Erkenntnis: Auf nem Singlecore ist das Management bis zu 10% langsamer als ohne QtConcurrent. Mit 5 Threads als maxCount 2% schneller. Heißt: idealThreadCount auf numCPUs setzen ist reine Willkür. Normalerweise laufen eh mehrere Prozesse neben dem Programm, und das Betriebssystem kann sich selbst aussuchen, ob jetzt ein neuer Kern "angepatzt" wird, oder ob die Threads auf ein und dem selben Kern laufen sollen.

Und ja, es stimmt schon, dass das Design stimmen muss, und nicht einfach eine Funktion ohne Hirn in nen Thread gepackt werden sollte :)
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

@franzf
welches system ?

IMHO gibts nen Riesenunnerschied im Verhalten zwischen Unix(artigen) und windows basierten OS an der Stelle.

Windows iss traditioniell sehr threadoptimiert. Wir haben scho mal tests laufen lassen, mit 4 10 100 Threads (aufm dualcore).
Das aufziehen und initiale verwalten der threads kostet schon, aber die reinen laufzeiten der operation unterscheiden sich gar ned mal so .
Also windows IMHO impft seinen scheduler scho, das ned so viele threadwechsel gemacht werden (sei denn man setzt alle seine threads auf Max Prio ^^)
Bzw kann man selber ziemlich filigran eingreifen.

Denk mal bei windows sollt der unnerschied ob 4 oder 8, 16 ... Threads parrallel laufen, ned so gravierend sein.
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

RHBaum hat geschrieben:@franzf
welches system ?

Code: Alles auswählen

$ uname -a
Linux localhost 2.6.34-gentoo-r1 #1 Tue Jun 22 11:14:43 CEST 2010 x86_64 AMD Athlon(tm) 64 Processor 3700+ AuthenticAMD GNU/Linux
Denk mal bei windows sollt der unnerschied ob 4 oder 8, 16 ... Threads parrallel laufen, ned so gravierend sein.
Es ist auch hier nicht sehr gravierend. Interessanterweise ist heute das Ergebnis andersrum. Meine Zahlen basieren nur auf dem qtconcurrent/wordcount-Example. Gestern war mein System frisch gebootet, nur Kate gestartet, da war das Ergebnis etwa
1 Thread: 0.9 -0.95x schneller als ohne MapReduce
5 Threads: 1.05x schneller als ohne MapReduve.
Heute sind die Zahlen fast genau andersrum (1 Thread hat jetzt 1.01x speedup ;)), laufen tut nur der Firefox. Unterschied von ~6-10% in der Laufzeit. Allerdings liest das Example >1100 Dateien, und da kann es auch Unterschiede dank Caching/konkurrierende Festplattenzugriffe etc. geben. Alles relativ...
upsala
Beiträge: 3946
Registriert: 5. Februar 2006 20:52
Wohnort: Landshut
Kontaktdaten:

Beitrag von upsala »

Du hast aber schon mehr als einen Test laufen lassen?
Antworten