Aprendizado de Threads em C++ e QT

Criando Threads com C++ Puro

Para entender o funcionamento de threads, é essencial compreender como a execução concorrente pode ser implementada em C++. Um exemplo básico envolve criar um thread que exibe valores incrementais em uma interface gráfica. No entanto, operações bloqueantes, como sleep(), executadas no thread principal causam travamentos na interface.

void Widget::on_pushButton_clicked() {
    int contador = 0;
    while (true) {
        ui->lcdNumber->display(QString::number(contador++));
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

Esse código, quando executado diretamente no slot do botão, bloqueia toda a interface, tornando-a inacessível. A solução é mover essa lógica para um thread separado.

Uso de std::thread com detach()

Utilizando std::thread, podemos iniciar uma nova thread que roda em paralelo:

void Widget::on_pushButton_clicked() {
    std::thread worker(&Widget::updateCounter, this);
    worker.detach(); // Libera o controle da thread
}

void Widget::updateCounter() {
    int valor = 0;
    while (true) {
        qDebug() << "Contador: " << valor;
        ui->lcdNumber->display(QString::number(valor++));
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

O uso de detach() permite que o thread execute em segundo plano sem bloquear o thread principal. Como o objeto std::thread é local, ele será automaticamente destruído ao final da função, evitando vazamentos de recursos.

Implementação com QThread

Apesar de funcional, o uso direto de std::thread não é ideal em projetos Qt por falta de integração com o sistema de eventos. A melhor prática é usar QThread.

Crie uma classe derivada de QThread e sobrescreva o método run():

class WorkerThread : public QThread {
    Q_OBJECT

public:
    explicit WorkerThread(QObject *parent = nullptr) : QThread(parent) {}

protected:
    void run() override {
        int valor = 0;
        while (valor <= 10) {
            emit progressUpdated(valor++);
            msleep(1000);
        }
    }

signals:
    void progressUpdated(int value);

~WorkerThread() {
    qDebug() << "Thread encerrada e recursos liberados.";
}
};

No widget principle:

Widget::Widget(QWidget *parent)
    : QWidget(parent), ui(new Ui::Widget) {
    ui->setupUi(this);
    worker = new WorkerThread(this);
    connect(worker, &WorkerThread::progressUpdated,
            [this](int val) {
                ui->lcdNumber->display(QString::number(val));
            });
}

void Widget::on_pushButton_clicked() {
    worker->start();
}

Widget::~Widget() {
    worker->quit();         // Sinaliza saída
    worker->wait();         // Espera término
    delete worker;
}

Utilizando moveToThread para múltiplos trabalhos

Em vez de herdar múltiplas classes de QThread, use moveToThread() para associar objetos a threads distintas:

MyTaskObject* task = new MyTaskObject;
QThread* thread = new QThread;
task->moveToThread(thread);

connect(thread, &QThread::started, task, &MyTaskObject::execute);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
connect(task, &MyTaskObject::completed, thread, &QThread::quit);

thread->start();

Isso permite que várias tarefas compartilhem uma mesma thread, evitando redundância e melhorando a organização do código.

Pool de Threads com QThreadPool

Para gerenciar múltiplas tarefas de forma eficiente, utilize QThreadPool:

class Task : public QRunnable {
public:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Executando tarefa:" << i;
            QThread::msleep(500);
        }
    }
};

// Uso
QThreadPool::globalInstance()->setMaxThreadCount(4);
QThreadPool::globalInstance()->start(new Task());
QThreadPool::globalInstance()->waitForDone();

Essa abordagem automatiza a criação, reutilização e limpeza de threads, otimizando o uso de recursos.

Conceitos Fundamentais

  • join vs detach: join() bloqueia até o término do thread; detach() libera o controle, mas exige cuidado com o tempo de vida do thread.
  • Funções anônimas (lambda): Expressões lambda permitem definir funções inline com acesso a variáveis do escopo externo.
  • Operador ()(): Em classes com operator(), tornam-se funções invocáveis como se fossem funções normais — úteis para threads.
  • try-catch: Essencial para tratar exceções em threads. O bloco try envolve código potencialmente falho; catch trata erros.
  • Métodos de criação de thread: Funções regulares, lambdas, funções-objeto (functors), e membros de classe podem ser usados como alvos de thread.

Tags: C++ Qt Multithreading QThread QThreadPool

Publicado em 8-23 14:36