
Software unbekannter Herkunft (SOUP) in Medizinprodukten
In den Projekten, die wir bei Somco für Hersteller medizinischer Geräte durchführen, taucht das Thema SOUP fast immer auf. Ob […]

Da Anwendungen immer komplexer werden und die Leistungserwartungen steigen, wird Multithreading unerlässlich. Meiner Erfahrung nach bietet Qt eine leistungsstarke – wenn auch manchmal verwirrende – Sammlung von Tools zur Arbeit mit Threads: eine plattformübergreifende Implementierung, die sich für viele Anwendungsszenarien im umfassenderen Qt-Framework eignet.
Dieser Artikel basiert auf einem Vortrag, den ich auf dem Qt World Summit und später beim Qt C++ Warsaw Meetup gehalten habe.
Falls Sie diese Veranstaltungen verpasst haben, ist dieser Beitrag eine schriftliche Version derselben Ideen – ein Überblick darüber, wie sich das Multithreading-Modell von Qt im Laufe der Zeit entwickelt hat:
Benötigen Sie Hilfe bei der Qt-Entwicklung und beim Multithreading? Holen Sie sich professionelle Unterstützung bei QThread, QtConcurrent oder TaskTree, um Ihren Workflow zu optimieren und die Leistung Ihrer Anwendung zu verbessern.
Threading macht Ihre Anwendung reaktionsschneller und schneller, indem aufwendige oder blockierende Operationen parallel ausgeführt werden. Das ist besonders wichtig bei GUIs, da der Haupt-Thread die Benutzereingaben flüssig verarbeiten kann, während ein sekundärer Thread rechenintensive Aufgaben übernimmt – gemäß einem Multithreaded-Design. In der Praxis verwendet eine multithreaded Anwendung einen Thread für die Hauptereignisschleife und einen anderen für die Datenverarbeitung, sodass threadbasierte Aufgaben die Benutzeroberfläche im Qt-Threading-System nicht blockieren.
Im Laufe der Jahre hat sich Qts Ansatz für Multithreading stark verändert. Frühere Versionen erforderten manuelle Thread-Verwaltung über QThread, aber spätere Releases führten QtConcurrent ein und danach experimentelle Frameworks wie TaskTree. Dies spiegelt die Qt-Philosophie wider – Kontrolle für Experten und Einfachheit für den Alltag. Wenn Sie sich den Quellcode dieser Bibliotheken ansehen, erkennen Sie eine klare Trennung zwischen Threading-Klassen und Logik.
QThread ist ein tatsächlicher Ausführungsthread. Er ähnelt std::thread, ist jedoch vollständig in das Qt-Ereignissystem integriert. Eine typische Implementierung verwendet eine QThread-Instanz, die den Thread und seinen Lebenszyklus verwaltet.
Anfangs bestand der typische Ansatz darin, QThread zu unterklassen und die Methode run() zu überschreiben:
class WorkerThread : public QThread {
Q_OBJECT
void run() override {
QString result;
/* ... aufwendige oder blockierende Operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread() {
auto workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
Zwar erhält man so vollständige Kontrolle über Thread-Erstellung und Lebenszyklus, jedoch entstehen dadurch auch viel Boilerplate-Code und mögliche Fallstricke – insbesondere beim Zusammenspiel von Signalen, Slots und Thread-Affinität.
Einer der häufigsten Fehler bei der Arbeit mit QThread ist das Missverständnis über Thread-Affinität. Jeder QObject gehört zu dem Thread, in dem er erstellt wurde. Wenn ein Objekt nach dem Einrichten von Verbindungen verschoben wird, können Signale und Slots in einem unerwarteten Kontext ausgeführt werden. Stellen Sie daher immer sicher, dass Objekte vor dem Start in den richtigen Thread verschoben werden – beispielsweise sorgt die Hauptanwendung dafür, dass ihre QObjects an den Hauptthread gebunden bleiben, um thread-sichere Ausführung zu gewährleisten.
Eine sauberere Alternative besteht darin, ein Worker-Objekt einzuführen und es in einen separaten Thread zu verschieben, anstatt QThread selbst zu unterklassen. Dieser Ansatz trennt Geschäftslogik von der Thread-Verwaltung und ermöglicht es in manchen Fällen, bestehenden Code mit minimalen Änderungen in einen Hintergrund-Thread zu verlagern.
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
// Lang laufende Aufgabe
emit progressChanged(30);
// Weitere Arbeit hier
emit workFinished();
}
signals:
void progressChanged(int progress);
void workFinished();
};
So wird es verwendet:
auto thread = new QThread(); auto worker = new Worker(); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Worker::doWork); connect(worker, &Worker::workFinished, thread, &QThread::quit); connect(worker, &Worker::workFinished, worker, &Worker::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(worker, &Worker::progressChanged, this, &MainWindow::updateProgressBar); thread->start();
Dieses Muster ist auch heute noch relevant für lang laufende oder kontinuierlich aktive Hintergrundaufgaben, die ihre eigene Ereignisschleife benötigen. Details wie Lebenszyklusverwaltung und Bereinigung sind entscheidend, wenn man mit Thread-Logik außerhalb der Hauptfunktion arbeitet.
Wenn Aufgaben nur kurzlebig sind, ist es ineffizient, mehrere Threads manuell zu verwalten. QThreadPool ermöglicht es, Threads wiederzuverwenden und die maximale Anzahl aktiver Worker festzulegen:
class MyTask : public QRunnable {
void run() override {
qDebug() << "Hallo Welt aus dem Thread";
}
};
QThreadPool::globalInstance()->start(new MyTask());
QThreadPool::globalInstance()->start([]() {
qDebug() << "Hallo Welt aus Lambda";
});
Thread-Pools reduzieren den Overhead und vereinfachen die Steuerung der Parallelität. Ihre Größe lässt sich ebenfalls anpassen:
auto pool = new QThreadPool(); pool->setMaxThreadCount(8); pool->start(new MyTask()); qDebug() << "Aktive Threads:" << pool->activeThreadCount();
Ab Qt 6.9 führen sowohl QThread als auch QThreadPool Quality-of-Service (QoS)-Kontrollen über die neue API setServiceLevel() ein.
Diese Funktion erlaubt es, dem Betriebssystem Hinweise zu geben, ob ein Thread Leistung oder Energieeffizienz bevorzugen soll – besonders wertvoll bei heterogenen CPU-Architekturen mit Performance- und Effizienz-Kernen (z. B. big.LITTLE).
QThread::currentThread()->setServiceLevel(QThread::QualityOfService::High); QThreadPool::globalInstance()->setServiceLevel(QThread::QualityOfService::Eco);
Diese Hinweise sind derzeit auf Windows und Apple-Plattformen wirksam, aber die API ist plattformübergreifend sicher – auf nicht unterstützten Systemen haben die Aufrufe keine Wirkung.
Diese Neuerung stellt einen subtilen, aber wichtigen Schritt in Richtung besserer Performance und Energieeffizienz im Multithreading-System von Qt dar.
Moderne Qt-Anwendungen müssen Threads selten manuell verwalten. Stattdessen bietet QtConcurrent High-Level-Funktionen wie run(), map() und filter(), die das Threading automatisch übernehmen.
int myFunction() { return 42; }
QFuture future = QtConcurrent::run(&myFunction);
Keine Thread-Objekte, keine Aufräumarbeiten – nur asynchrone Ausführung. Rückgabewerte werden elegant in einem QFuture verpackt, sodass Sie auf den Abschluss warten oder Ergebnisse überwachen können.
QFuture repräsentiert das Ergebnis einer asynchronen Berechnung. Es erlaubt Statusprüfungen, Verkettung und Abbruch und kann auf thread-sichere Weise in Qt-GUI-Updates integriert werden.
QFuturefuture = QtConcurrent::run([]() { return 42; }); qDebug() << "Die Antwort ist:" << future.result(); // blockiert bis fertig
Für nicht blockierende Aktualisierungen verwenden Sie QFutureWatcher:
QFuturefuture = QtConcurrent::run([]() { return 42; }); auto watcher = new QFutureWatcher (); watcher->setFuture(future); connect(watcher, &QFutureWatcher ::finished, [=]() { qDebug() << "Die Antwort ist:" << watcher->result(); watcher->deleteLater(); });
Seit Qt 6 können Sie asynchrone Operationen mit .then() verketten:
QFuturefuture = QtConcurrent::run([]() { return 42; }) .then([](int result) { return static_cast (result); }) .then([](QFuture fut) { return QString("Das Endergebnis ist %1").arg(fut.result()); });
Das ist modern und elegant – fast wie async/await.
QtConcurrent unterstützt jetzt auch Ausnahmen und Promises. Das ist entscheidend für robuste Multithreading-Pipelines. Durch das Ableiten von QException können Sie Fehler sicher über Threads hinweg propagieren – unerlässlich für robuste Threading-Klassen:
class MyException : public QException {
public:
void raise() const override { throw *this; }
MyException *clone() const override { return new MyException(*this); }
};
Und so behandeln Sie sie elegant:
QFuturefuture = QtConcurrent::run([]() -> int { throw MyException(); }) .onFailed([](const MyException&) { qDebug() << "MyException ist aufgetreten"; return -1; });
Sie können den asynchronen Fortschritt sogar manuell mit QPromise verwalten:
QFuturerunWithProgress() { return QtConcurrent::run([](QPromise &promise) { promise.setProgressRange(0, 100); for (int i = 0; i <= 100; ++i) { if (promise.isCanceled()) return; promise.setProgressValue(i); QThread::msleep(20); } promise.addResult(42); }); }
Während QtConcurrent vieles vereinfacht, erfordern komplexe asynchrone Workflows dennoch eine koordinierte Steuerung. Genau hier kommt TaskTree ins Spiel – ein Framework, das für Qt Creator entwickelt wurde und Aufgabenabhängigkeiten, Fortsetzungen und Fehlerbehandlungsrichtlinien verwaltet.
Hier ist ein einfaches Beispiel:
const Group recipe {
sequential, // Ausführungsmodus
stopOnError, // Workflow-Richtlinie
NetworkQueryTask(...),
Group {
parallel,
ConcurrentCallTask(...),
ConcurrentCallTask(...)
}
};
taskTree->setRecipe(recipe);
taskTree->start();
Es unterstützt sogar logische Operatoren und Bedingungen:
const Group recipe {
task1 && task2,
task3 || task4,
!task
};
const Group conditional {
If (condTask1) >> Then { bodyTask1 }
>> ElseIf (condTask2) >> Then { bodyTask2 }
>> Else { bodyTask3 }
};
Das ist der nächste Schritt in der Weiterentwicklung des Multithreadings in Qt – weniger Boilerplate und mehr Lesbarkeit mit Fokus auf Systemdesign.
Als Qt-Beitragender habe ich kürzlich die Verbesserung QTBUG-131142 – „Make TaskTree a public API“ gemeldet und vorgeschlagen.
Der Vorschlag wurde angenommen und wird in Qt 6.11 implementiert, sodass TaskTree für Entwickler besser zugänglich ist, die es in ihren eigenen Projekten verwenden möchten.
Es ist großartig zu sehen, wie sich dieses Framework von einer internen Komponente von Qt Creator zu einer öffentlichen Qt-API entwickelt.
| Aspekt | QThread / Worker-Muster | QtConcurrent / QFuture | TaskTree |
|---|---|---|---|
| Abstraktionsebene | Niedrig – manuelle Thread- und Objektverwaltung | Mittel – automatisches Threading, manuelle Logik | Hoch – vollständig deklarative Aufgabenorchestrierung |
| Typischer Anwendungsfall | Länger laufende oder dauerhaft aktive Hintergrundaufgaben | Kurzlebige oder parallelisierbare Operationen (Datenverarbeitung, I/O) | Komplexe Workflows mit Abhängigkeiten, Bedingungen oder Verzweigungen |
| Benutzerfreundlichkeit | Schwierig – Boilerplate und Lebenszyklusverwaltung erforderlich | Einfach – minimaler Code, automatische Bereinigung | Sehr einfach – konfigurationsbasiert, kein Thread-Code |
| Thread-Kontrolle | Volle Kontrolle (Start, Stopp, Pause, Ereignisschleifen) | Begrenzt – intern durch QtConcurrent verwaltet | Vollständig durch das Framework automatisiert |
| Fehlerbehandlung | Manuell (Signale, Mutexes, try/catch) | Integriert über QFuture / QException / QPromise | Integriert – unterstützt Workflow-Richtlinien (stopOnError, continueOnError) |
| Fortschrittsanzeige | Manuell über benutzerdefinierte Signale | Integriert über QPromise | Integriert und über Aufgaben hinweg aggregiert |
| Performance-Optimierung | Volle Kontrolle, erfordert jedoch Feintuning | Automatisch über Thread-Pools verwaltet | Richtliniengesteuert (parallel/sequenziell) |
| Am besten geeignet für | Feingranulare Kontrolle oder Legacy-Code | Moderne Anwendungen mit asynchroner Ausführung | Große, modulare Systeme mit deklarativer Parallelität |
| Beispiel in Qt | QThread, QObject::moveToThread() | QtConcurrent::run(), QFutureWatcher | TaskTree (verwendet von Qt Creator) |
Jeder Ansatz stellt ein unterschiedliches Gleichgewicht zwischen Kontrolle, Sicherheit und Einfachheit dar.
Die beste Wahl hängt nicht von persönlichen Vorlieben ab, sondern von der Art Ihrer Aufgaben und davon, wie viel Sie von der Thread-Verwaltung abstrahieren möchten.
Von QThread bis TaskTree hat Qt einen weiten Weg zurückgelegt, um Multithreading zu vereinfachen. Jede Schicht baut auf der vorherigen auf, abstrahiert die Komplexität und bewahrt gleichzeitig die Leistungsfähigkeit. Wenn Sie immer noch Threads manuell verwalten, probieren Sie QtConcurrent und TaskTree – Ihr zukünftiges Ich (und Ihr UI-Thread) wird es Ihnen danken.
Multithreading kann anfangs einschüchternd sein – ich habe selbst alle klassischen Fehler gemacht. Aber sobald man das Qt-Modell versteht und das richtige Abstraktionsniveau für seinen Anwendungsfall wählt, ist es eines der schönsten und robustesten Frameworks für nebenläufige Programmierung.
Benötigen Sie Hilfe bei Ihrem Qt-Projekt?
Kontaktieren Sie uns für professionelle Unterstützung bei der Gestaltung, Optimierung oder Skalierung Ihrer Qt-Anwendungen – von der Architektur über die Performance hinaus.
Kommen wir zur Sache: Es ist eine Herausforderung, Top-Qt-QML-Entwickler zu finden. Helfen Sie sich selbst und starten Sie die Zusammenarbeit mit Somco Software – echten Experten im Qt C++ Framework.
Entdecken Sie unsere Fähigkeiten!
In den Projekten, die wir bei Somco für Hersteller medizinischer Geräte durchführen, taucht das Thema SOUP fast immer auf. Ob […]

Robot Operating System (ROS) ist ein Open-Source-Framework, das sich zum De-facto-Standard für die Entwicklung von Robotiksoftware entwickelt hat. Moderne Roboter […]

Software as a Medical Device (SaMD) bezeichnet Software, die für medizinische Zwecke bestimmt ist (wie z. B. zur Diagnose, Behandlung, Heilung, […]