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_variableestd::atomicsão essenciais para proteger dados compartilhados e evitar condições de corrida. - Threads e Tarefas Assíncronas:
std::asyncestd::futurepermitem 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;
}