Wenn du den Signal-Slot-Mechanismus von Qt als Queued-connection verwendest, dann wird das intern als Event verarbeitet, der zum aktuellen Thread hinzugefügt wird. Wenn du dann den Eventloop durchlaufen lässt, werden die Signale verteilt.
Zuerst mal ein paar Tips wie ich es implementieren würde und dann noch ein Vergleich zwischen der Methode mit Signals und Slots und der von mir vorgeschlagenen mit Semaphores.
Die Consumer dürfen erst innerhalb eines QThreads erstellt werden.
Code: Alles auswählen
class ConsumerN: public QObject {
Q_OBJECT
ConsumerN();
public slots:
doSomething(data);
}
class ConsumerNThread {
ConsumerNThread();
ConsumerN* consumer;
void run() {
consumer = new ConsumerN();
//Producerthread benachrichtigen, dass Consumer initialisiert ist
exec();
}
}
Du brauchst eigentlich keine Flag um irgendeinen Befehl im Thread aufzurufen, es sollte reichen mit Signals den Consumer aufzurufen. Wenn jetzt der Producer per Signal dem Consumer die Daten gibt, werden diese abgearbeitet und wenn der Consumer gerade arbeitet, werden sie eben zur Queue hinzugefügt.
Das bedeutet, dass wenn die GUI oder irgendwer anderes dem Consumer irgendwas per Signal mitteilt, dann wird dies erst bearbeitet, nachdem die vorherige Arbeit fertig ist.
Zum überprüfen, ob noch Signals in der Warteschleife sind, kannst du bei Queued-Connections überprüfen, ob noch Events abgearbeitet werden müssen, da die Signale für Queued-Connections als QMetaCallEvents in die Eventschleife hinzugefügt werden.
Code: Alles auswählen
if(QCoreApplication::hasPendingEvents())
//do something
Damit kannst du am Ende überprüfen, ob die Threads noch Arbeit zu verrichten haben und dann solange warten bis sie fertig sind.
Von den Consumern aus kannst du eigentlich ohne Probleme direkt auf die GUI, entweder über nen Pointer(Mutex nötig!!) oder per Signal, zugreifen.
Die Threads würde ich in einer Liste, eventuell nach Typ geordnet, aufbewahren. Wenn du dynamisch, je nach vorhandener Arbeit mehrere Consumer vom selben Typ erstellen willst, dann musst du mitzählen, wie viel Arbeit die einzelnen Threadtypen haben und ggf. einen weiteren Thread erstellen. Allerdings solltest du die Threadanzahl pro Typ auf QThread::idealThreadCount() beschränken, ansonsten könnten bei hoher CPU-Auslastung unnötig viele Threads erzeugt werden, die dann nur bremsen. Die einmal gestarteten Threads kannst du einfach warten lassen und dann wieder benutzen oder nach einer längeren Zeit, in der sie nichts getan haben beenden.
Damit der Producer-Thread auch die Signals verarbeitet, muss er immer wieder, am besten nach jedem Datenblock, den Eventloop durchlaufen lassen. Allerdings können die dadurch gestarteten Slots nur Variablen innerhalb der Klasse verändern, was heißt, dass sie nur indirekt die Arbeit vom Producer verändern können.
Beim Verarbeiten der Daten musst du aufpassen, dass wenn du QObjects verwendest, die Parents für diese im selben Thread sein müssen, ansonsten weigern sich diese Objekte zu funktionieren, was mir schon einige Probleme verursacht hat.
Dein Ansatz sollte (eigentlich ?) funktionieren.
Da immer wieder der Eventloop gestartet wird werden auch alle Anweisungen von der GUI irgendwann verarbeitet.
Als Ablauf hättest du dann so was wie:
Producer --> Daten lesen, Befehl irgendwie zwischenspeichern, bis Consumer fertig
Consumer0 starten --> Producer Initialisierung melden
Producer --> Anweisung an Consumer0 senden --> Consumer0 arbeitet
Producer liest weitere Daten --> wie an Consumer0 zuteilen --> Befehl muss warten
Consumer0 berechnet ersten Befehl fertig, Rückmeldung an Producer --> zweiten Befehl abarbeiten
Producer verarbeitet Rückmeldung --> ...
Producer ist fertig --> warten bis alle Daten verarbeitet sind --> Threads beenden
Meinen Beispielcode aus dem vorherigen Post, kannst du ebenfalls so anpassen, dass er deine Wünsche erfüllt, dazu müsstest du nur die QSemaphores in einer Liste sammeln und den entsprechenden an den passende Consumer weiterleiten.
Jetzt noch eine Liste mit den Vor- und Nachteilen der beiden Methoden (keine Garantie für Vollständigkeit

):
Signal-Slot-Synchronisierung:
+/-benötigt QObject
-wenn die Daten auch QObjects verwenden, sind die an den Thread gebunden
-Signale werden in fester Reihenfolge ausgeführt, keine Priorisierung möglich (ist wahrscheinlich nicht notwendig)
(wenn die Befehle priorisiert werden können sollen, dann musst du die Daten zwischenspeichern und dann gewählt ausführen, was dann ca ähnlich komplex wird wie mit QSemaphore, allerdings geht dann der Hauptvorteil der Signals verloren, dass Qt selbst die gewünschten Funktionen aufruft)
-Befehle müssen beim Start eines Threads extra zwischengespeichert werden
+Funktionen können direkt aufgerufen werden, ohne eigenen Zusatzcode
+Synchronisierung wird größtenteils von Qt selbst gehandhabt
-Befehle müssen vom eigenen Code auf die Consumer verteilt werden --> sehr schwierig, wenn z.B. 2 gleiche Consumer vorhanden sind (evtl. mit zusätzlichem Synchronisierungsthread zu implementieren --> Synchronisierungsthread bekommt Befehle und vergibt sie an die entsprechenden Consumer. Der Thread sendet immer nur einen Befehl an den Consumer und verteilt den nächsten, wenn dieser fertig ist. Der Producer sollte nicht dazu benutzt werden die Daten zu verteilen, da ansonsten zu große Wartezeiten entstehen, hierbei gibt es wieder das selbe Problem, wie beim Priorisierungsproblem angemerkt. Auch solltest du den Synchronisierungsthread dann selbst das Starten weiterer Threads durchführen lassen)
QSemaphore-Synchronisierung (es sollte ein Semaphore reichen, der die Anzahl der verfügbaren Befehle als Wert hat):
+/-verwendet/benötigt kein QObject
+Befehle lassen sich in beliebiger Reihenfolge ausführen
-Funktionen können nur indirekt aufgerufen werden (am besten Codenummern für die einzelnen Funktionen verteilen, wie beim moc-Code)
+Keine Zwischenspeicherung beim Threadstart notwendig
-schwieriger zu synchronisieren --> viel Code und viele Mutexes notwendig, zudem muss man aufpassen, dass der Thread nicht durch den Semaphore blockiert wird (hier wäre es nützlich, wenn man direkt am Thread feststellen könnte, ob ein Thread pausiert oder nicht; dazu am besten selbst eine Flag setzen die anzeigt, ob gewartet wird oder nicht, dann kann der Producer dies ähnlich wie bei den Signals und Slots überprüfen und den Thread ggf. beenden)
+verteilt selbstständig die Arbeitslast --> skaliert besser als mit Signals
-->Für einfache Synchronisierungsarbeiten sind Signals die einfachere Lösung, wenn es allerdings komplizierter wird, würde ich Semaphores verwenden.
Beide Methoden zu mischen ist meiner Meinung nach sinnlos, da die Semaphores den Thread pausieren, bis neue Befehle vorhanden sind und zum anderen die Signals beim informieren über neue Befehle ohnehin langsamer wären, denn die Semaphores werden praktisch sofort entsperrt.
Mit Semaphores kannst du ebenfalls einen Callback machen. Am besten die Callbacks in eine Liste im Producer schreiben, die dieser dann immer wieder prüft. Dies funktioniert fast gleich wie wenn du Signals verwendest, nur musst du wie in den Threads, das Aufrufen der richtigen Funktion selbst übernehmen. Bei den Signals lässt der Producer immer wieder den Eventloop abarbeiten, der hier durch die Liste ersetzt ist. Beim selbstgeschriebenen Callback kannst du auch die Variablen direkt innerhalb der Funktion verändern, was evtl. nützlich sein könnte.
So das wars erst mal. Ich hoffe mal, dass mein Roman

verständlich ist. Wenn jemand noch Ergänzungen zu meiner Liste hat, bitte posten.