Thread pools offer an efficient approach to managing concurrent tasks by reusing a fixed number of threads rather then repeatedly creating and destroying them. This minimizes overhead and improves performance, especially when handling numerous short-lived operations. In Qt, the QThreadPool and QRunnable classes provide a high-level interface for implementing such thread pool–based concurrency.
Understanding QRunnable
QRunnable is an abstract base class representing a unit of work that can be executed by a thread pool. Unlike QThread, it does not manage thread lifecycle directly. Instead, it encapsulates logic within its pure virtual run() method. Key features include:
run(): Must be overridden to define task behavior.setAutoDelete(bool): When enabled (default isfalse), the runnable deletes itself after execution, preventing memory leaks.setPriority(QThread::Priority): Allows setting execution priority relative to other runnables.
Managing Threads with QThreadPool
QThreadPool manages a collection of reusable threads and schedules QRunnable instances across them. It handles thread creation, reuse, and cleanup automatically. Common methods include:
globalInstance(): Returns a shared, application-wide thread pool instance.start(QRunnable*): Queues a task for execution.setMaxThreadCount(int): Limits the number of concurrently running threads to avoid resource exhaustion.waitForDone(): Blocks until all queued tasks complete—useful for synchronization before shutdown.
Practical Implementation
The following example demonstrates submitting multiple retry-capable tasks to a thread pool.
worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QRunnable>
#include <QString>
#include <QDebug>
#include <QThread>
class Task : public QRunnable {
public:
explicit Task(const QString& name, int maxRetries = 3);
void run() override;
private:
bool simulateOperation();
QString m_name;
int m_maxRetries;
};
#endif // WORKER_H
worker.cpp
#include "worker.h"
#include <QRandomGenerator>
Task::Task(const QString& name, int maxRetries)
: m_name(name), m_maxRetries(maxRetries) {
setAutoDelete(true);
}
void Task::run() {
int attempt = 0;
bool succeeded = false;
while (attempt < m_maxRetries && !succeeded) {
++attempt;
qDebug() << "Executing:" << m_name << "| Attempt:" << attempt;
succeeded = simulateOperation();
if (!succeeded) {
qDebug() << "Failed, retrying:" << m_name;
QThread::sleep(2);
}
}
if (succeeded) {
qDebug() << "Completed:" << m_name;
} else {
qDebug() << "Aborted after retries:" << m_name;
}
}
bool Task::simulateOperation() {
return QRandomGenerator::global()->bounded(2) == 1; // 50% success rate
}
main.cpp
#include <QCoreApplication>
#include <QThreadPool>
#include "worker.h"
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
auto* pool = QThreadPool::globalInstance();
pool->setMaxThreadCount(4);
pool->start(new Task("Download A"));
pool->start(new Task("Process B"));
pool->start(new Task("Validate C"));
pool->start(new Task("Upload D", 2)); // Only 2 retries
pool->start(new Task("Sync E"));
pool->waitForDone();
return 0;
}
Sample output may resemble:
Executing: "Download A" | Attempt: 1
Completed: "Download A"
Executing: "Process B" | Attempt: 1
Completed: "Process B"
Executing: "Validate C" | Attempt: 1
Completed: "Validate C"
Executing: "Upload D" | Attempt: 1
Failed, retrying: "Upload D"
Executing: "Upload D" | Attempt: 2
Completed: "Upload D"
Executing: "Sync E" | Attempt: 1
Completed: "Sync E"
This pattern enables scalable, maintainable concurrency: tasks are decoupled from thread management, automatic cleanup prevents leaks, and the thread pool ensures optimal resource utilization without manual intervention.