Ciclo de Vida e Persistência de Threads em C++

Criação de Threads e o Problema do Ciclo de Vida

Em C++11 e superior, a criação de threads é simplificada pelo uso da classe std::thread. No entanto, ao instanciar um objeto de thread, o programador assume a responsabilidade de gerenciar seu ciclo de vida. Se um objeto std::thread for destruído enquanto a thread associada ainda estiver em execução, e nenhuma decisão de encerraemnto tiver sido tomada, o programa chamará std::terminate(), resultando em um abortamento imediato.

#include <iostream>
#include <thread>

void tarefa_background() {
    std::cout << "Executando tarefa em background\n";
}

int main() {
    std::thread worker(tarefa_background);
    // Se o objeto 'worker' for destruído neste ponto sem join() ou detach(),
    // o programa chamará std::terminate() e abortará.
    // worker.join(); // Descomente para evitar o crash
    return 0;
}

Estratégias de Encerramento: Join e Detach

Para evitar o terminamento abrupto, é obrigatório definir como a thread será finalizada em relação ao fluxo de execução principal. O C++ oferece duas abordagens distintas: join() e detach().

Modo Join (Síncrono)

Ao chamar join(), a thread atual bloqueia e aguarda até que a thread associada conclua sua execução. Uma prática recomendada para garantir a segurança de recursos é utilizar o padrão RAII (Resource Acquisition Is Initialization), encapsulando a chamada de join() no destrutor de uma classe auxiliar.

#include <iostream>
#include <thread>
#include <chrono>

class ThreadGuard {
private:
    std::thread& t;
public:
    explicit ThreadGuard(std::thread& t_) : t(t_) {}
    
    ~ThreadGuard() {
        if (t.joinable()) {
            t.join();
        }
    }
    
    ThreadGuard(const ThreadGuard&) = delete;
    ThreadGuard& operator=(const ThreadGuard&) = delete;
};

int main() {
    std::thread t([]{
        std::cout << "Início da thread\n";
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << "Fim da thread\n";
    });

    ThreadGuard guard(t);
    std::cout << "Fim do main\n";
    return 0;
}

Modo Detach (Assíncrono)

O método detach() separa a thread do objeto std::thread, permitindo que ela continue executando em background de forma independente. Uma vez separada, não é mais possível aguardar sua conclusão. É crucial entender que a thread detachada ainda pertence ao processo; se a thread principal (ou o processo) for encerrada, a thread em background será brutalmente interrompida pelo sistema operacional.

#include <iostream>
#include <thread>
#include <fstream>
#include <chrono>

void gravar_log(const std::string& caminho, const std::string& msg) {
    std::ofstream arquivo(caminho, std::ios::app);
    if (arquivo.is_open()) {
        arquivo << msg << "\n";
    }
}

int main() {
    const std::string caminho_log = "/tmp/log_aplicacao.txt";

    std::thread bg_thread([caminho_log]{
        std::cout << "Thread iniciada\n";
        for (int i = 0; i < 3; ++i) {
            gravar_log(caminho_log, "Entrada de log " + std::to_string(i));
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        std::cout << "Thread finalizada\n";
    });

    bg_thread.detach();
    std::cout << "Main finalizado\n";
    // O processo encerra aqui, matando a thread detachada antes de terminar o loop.
    return 0;
}

Ecossistema de Gerenciamento de Concorrência

Além do controle básico de ciclo de vida, o C++ fornece um conjunto robusto de ferramentas para gerenciar a concorrência:

  • Sincronização: Mecanismos como std::mutex, std::condition_variable e std::atomic são essenciais para proteger dados compartilhados e evitar condições de corrida.
  • Threads e Tarefas Assíncronas: std::async e std::future permitem delegar tarefas e recuperar seus resultados de forma assíncrona, abstraindo a criação manual de threads.
  • Estruturas de Dados Concorrentes: O uso de tipos atômicos e filas protegidas por mutex é fundamental para manter a consistência dos dados em ambientes multithread.

Persistência de Thread (Thread Keep-Alive)

Para manter uma thread viva e processando tarefas de forma contínua sem consumir CPU desnecessariamente, a abordagem padrão é implementar um loop de eventos. A thread permanece bloqueada em uma variável de condição, aguardando a chegada de novas tarefas ou um sinal de encerramento.

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <atomic>

class WorkerThread {
private:
    std::thread worker;
    std::queue<std::function<void()>> tarefas;
    std::mutex mtx;
    std::condition_variable cv;
    std::atomic<bool> keep_alive{true};

public:
    void iniciar() {
        worker = std::thread([this] {
            while (keep_alive) {
                std::function<void()> tarefa;
                {
                    std::unique_lock<std::mutex> lock(mtx);
                    cv.wait_for(lock, std::chrono::milliseconds(100), [this] {
                        return !tarefas.empty() || !keep_alive;
                    });
                    
                    if (!keep_alive && tarefas.empty()) break;
                    
                    if (!tarefas.empty()) {
                        tarefa = std::move(tarefas.front());
                        tarefas.pop();
                    }
                }
                if (tarefa) tarefa();
            }
        });
    }

    void adicionar_tarefa(std::function<void()> tarefa) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            tarefas.push(std::move(tarefa));
        }
        cv.notify_one();
    }

    void parar() {
        keep_alive = false;
        cv.notify_one();
        if (worker.joinable()) {
            worker.join();
        }
    }
};

int main() {
    WorkerThread pool;
    pool.iniciar();

    for (int i = 0; i < 3; ++i) {
        pool.adicionar_tarefa([i] {
            std::cout << "Executando tarefa " << i << "\n";
        });
    }

    std::this_thread::sleep_for(std::chrono::seconds(1));
    pool.parar();
    return 0;
}

Tags: cpp std-thread Multithreading condition-variable RAII

Publicado em 7-12 19:33