Besuchen Sie uns auf der Embedded World 2026 | 10–12.03, Nürnberg, Deutschland. Mehr erfahren .

Qt & QML Development
2025-11-07
7 Minuten Lesezeit

Qt-Multithreading meistern, ohne den Verstand zu verlieren

Adam Sowa
Adam Sowa Chief Technology Officer

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 Poland 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;
        /* ... expensive or blocking 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() {
        // Long-running task
        emit progressChanged(30);
        // More work here
        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() << "Hello world from thread";
    }
};

QThreadPool::globalInstance()->start(new MyTask());
QThreadPool::globalInstance()->start([]() {
    qDebug() << "Hello world from 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() << "Active 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<int> future = QtConcurrent::run(&myFunction);
</int>

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<int> future = QtConcurrent::run([]() { return 42; });
qDebug() << "The answer is:" << future.result(); // blocks until finished
</int>

Für nicht blockierende Aktualisierungen verwenden Sie QFutureWatcher:

QFuture<int> future = QtConcurrent::run([]() { return 42; });
auto watcher = new QFutureWatcher<int>();
watcher->setFuture(future);
connect(watcher, &QFutureWatcher<int>::finished, [=]() {
    qDebug() << "The answer is:" << watcher->result();
    watcher->deleteLater();
});
</int></int></int>

Verkettung mit Fortsetzungen (Continuations)

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

QFuture<qstring> future = QtConcurrent::run([]() { return 42; })
    .then([](int result) {
        return static_cast<double>(result);
    })
    .then([](QFuture<double> fut) {
        return QString("The final result is %1").arg(fut.result());
    });
</double></double></qstring>

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<int> future = QtConcurrent::run([]() -> int {
    throw MyException();
})
.onFailed([](const MyException&) {
    qDebug() << "MyException occurred";
    return -1;
});
</int>

Sie können den asynchronen Fortschritt sogar manuell mit QPromise verwalten:

QFuture<int> runWithProgress() {
    return QtConcurrent::run([](QPromise<int> &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);
    });
}
</int></int>

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, // execution mode
    stopOnError, // workflow policy
    NetworkQueryTask(...),
    Group {
        parallel,
        ConcurrentCallTask<qimage>(...),
        ConcurrentCallTask<qimage>(...)
    }
};

taskTree->setRecipe(recipe);
taskTree->start();
</qimage></qimage>

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.

Kontaktieren Sie uns

Wir beantworten jede Anfrage und finden die optimale Strategie für den Erfolg Ihres Projekts.

Füllen Sie das Formular aus, und wir melden uns in Kürze bei Ihnen.

Lukas Kosiński

Lukas Kosiński

Chief Executive Officer

Der Administrator der personenbezogenen Daten ist Somco Software sp. z o.o., ul. Gen. Ottokara Brzoza-Brzeziny 13, 05-220 Zielonka, KRS: 855688. Die personenbezogenen Daten werden verarbeitet, um die im Kontaktformular gestellte Anfrage zu beantworten. Weitere Informationen, einschließlich einer Beschreibung der Rechte der betroffenen Personen, finden Sie in der Datenschutzerklärung .