Qt-Multithreading meistern, ohne den Verstand zu verlieren

Qt QML-Entwicklung
2025-11-07
9 Minuten
Qt-Multithreading

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:

 

  • von Low-Level-APIs wie QThread,
  • über High-Level-Hilfsmittel wie QtConcurrent,
  • bis hin zu modernen und abstrakten Ansätzen wie TaskTree.

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.

 

Warum Threading?

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.

 

Die Vergangenheit: Low-Level-APIs

 

Direkte Arbeit mit QThread

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.

 

Das Worker-Objekt-Muster

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.

 

Thread-Pools mit QThreadPool

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();

 

Moderne Verbesserungen in Qt 6.9

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.

 

Die Gegenwart: High-Level-APIs

 

QtConcurrent

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.

 

Ergebnisverwaltung mit QFuture

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.

 

QFuture future = QtConcurrent::run([]() { return 42; });
qDebug() << "Die Antwort ist:" << future.result(); // blockiert bis fertig

Für nicht blockierende Aktualisierungen verwenden Sie QFutureWatcher:

 

QFuture future = QtConcurrent::run([]() { return 42; });
auto watcher = new QFutureWatcher();
watcher->setFuture(future);
connect(watcher, &QFutureWatcher::finished, [=]() {
    qDebug() << "Die Antwort ist:" << watcher->result();
    watcher->deleteLater();
});

 

Verkettung mit Fortsetzungen (Continuations)

Seit Qt 6 können Sie asynchrone Operationen mit .then() verketten:

 

QFuture future = 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.

 

Fehlerbehandlung und QPromise

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:

 

QFuture future = 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:

 

QFuture runWithProgress() {
    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);
    });
}

 

Die Zukunft: TaskTree

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.

 

Community-Beiträge und der Weg nach vorn

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.

 

Vergleich von Multithreading-Ansätzen in Qt

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.

 

Häufige Fallstricke und Best Practices

  • Vermeiden Sie es, result() für einen laufenden oder abgebrochenen QFuture aufzurufen (es blockiert unendlich)
  • Überprüfen Sie immer isValid(), bevor Sie Ergebnisse auslesen.
  • Erstellen Sie keine GUI-Elemente aus Worker-Threads, da der GUI-Thread exklusiv dafür vorgesehen ist.
  • Verwenden Sie bei Bedarf Signale oder QMetaObject::invokeMethod() für die Kommunikation zwischen Threads.
  • Bevorzugen Sie QtConcurrent oder TaskTree für kurze Aufgaben – sie übernehmen die Synchronisation für Sie.

 

Fazit

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.

Scythe-Studio - Chief Technology Officer

Przemysław Sowa Chief Technology Officer

Brauchen Sie Qt QML-Entwicklungsdienste?

service partner

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!

Neueste Artikel

[ 156 ]