Introdução à concorrência e multithreading em C++
Aplicações modernas exigem desempenho máximo. Concorrência e multithreading em C++ permitem usar eficientemente processadores multi-core, acelerando a execução de tarefas. Sem entender esses conceitos, é impossível criar sistemas de alta carga, processadores de dados ou motores de jogos.
Neste artigo, vamos analisar os principais mecanismos de multithreading em C++, começando com o básico std::thread e terminando com padrões avançados. Você aprenderá como evitar condições de corrida, sincronizar threads corretamente e escrever código paralelo seguro.
Fundamentos do multithreading: std::thread e std::jthread
A classe std::thread (C++11) é a base da programação multithread. Ela permite executar uma função ou objeto callable em uma thread separada. Vejamos um exemplo simples:
#include <iostream>#include <thread>
void worker(int id) { std::cout << "Thread " << id << " iniciou o trabalho"; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Thread " << id << " finalizada";}
int main() { std::thread t1(worker, 1); std::thread t2(worker, 2); t1.join(); t2.join(); return 0;}Pontos-chave:
- join() — a thread principal espera a thread filha terminar
- detach() — a thread continua executando independentemente (risco de vazamento de recursos)
- std::jthread (C++20) — chama automaticamente join() no destrutor
Sempre verifique joinable() antes de chamar join() para evitar junção dupla.
Sincronização de threads: mutexes e locks
O principal problema do multithreading são as condições de corrida, quando várias threads modificam simultaneamente uma variável compartilhada. Para proteção, usamos mutexes.
std::mutex e std::lock_guard
#include <iostream>#include <thread>#include <mutex>
std::mutex mtx;int counter = 0;
void increment() { for (int i = 0; i < 100000; ++i) { std::lock_guard<std::mutex> lock(mtx); ++counter; }}
int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Resultado: " << counter << ""; // 200000 return 0;}std::lock_guard libera automaticamente o mutex ao sair do escopo. Para controle mais flexível, use std::unique_lock.
Deadlock e std::scoped_lock
Deadlock ocorre quando duas threads esperam uma pela outra. A solução é usar std::scoped_lock (C++17), que captura vários mutexes atomicamente:
std::mutex m1, m2;
void safe_function() { std::scoped_lock lock(m1, m2); // trabalho seguro com dois recursos}Algoritmos paralelos e std::async
Para executar tarefas assíncronas sem gerenciamento manual de threads, use std::async. Ele retorna um std::future, do qual você pode obter o resultado.
#include <iostream>#include <future>#include <chrono>
int compute(int n) { std::this_thread::sleep_for(std::chrono::seconds(n)); return n * n;}
int main() { auto future1 = std::async(std::launch::async, compute, 2); auto future2 = std::async(std::launch::async, compute, 3); std::cout << "Resultados: " << future1.get() << ", " << future2.get() << ""; // 4, 9 return 0;}Políticas de lançamento:
std::launch::async— execução em uma nova threadstd::launch::deferred— execução adiada (lazy)