Concorrência e Multithreading em C++: Nível Avançado

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Concorrência e Multithreading em C++: dos Fundamentos às Técnicas Avançadas



Os aplicativos modernos exigem cada vez mais desempenho máximo, especialmente ao trabalhar com processadores multi-core. C++ oferece ferramentas poderosas para trabalhar com concorrência e multithreading, desde C++11 até os padrões mais recentes. Neste artigo, abordaremos aspectos avançados de multithreading: desde o uso correto de mutexes e operações atômicas até padrões complexos de sincronização. O material é voltado para desenvolvedores já familiarizados com os fundamentos de threads em C++.



1. Gerenciamento de Threads: std::thread e std::jthread



A classe std::thread é a base da programação multithread em C++. No entanto, em um nível avançado, é importante não apenas criar uma thread, mas também gerenciar corretamente seu ciclo de vida. Com C++20, surgiu o std::jthread, que se junta automaticamente ao ser destruído, prevenindo vazamentos de recursos.



Exemplo de uso de std::jthread:



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

void worker(int id) { std::this_thread::sleep_for(std::chrono::milliseconds(100 * id)); std::cout << "Thread " << id << " finalizada";}

int main() { // std::jthread se junta automaticamente ao sair do escopo std::jthread t1(worker, 1); std::jthread t2(worker, 2); std::jthread t3(worker, 3); // Não é necessário chamar join() ou detach() explicitamente // Ao destruir t1, t2, t3, main aguarda sua conclusão std::cout << "Thread principal continua trabalhando"; return 0;}


É importante lembrar: std::jthread também suporta o mecanismo de parada via std::stop_token, permitindo encerrar com segurança tarefas longas em segundo plano.



2. Sincronização de Acesso: Mutexes, Locks e Variáveis de Condição



O principal problema do multithreading são as condições de corrida. Para evitá-las, usamos mutexes. Em um nível avançado, recomenda-se usar std::scoped_lock (C++17) para capturar vários mutexes sem risco de deadlock.



Exemplo com std::scoped_lock:



#include <iostream>#include <mutex>#include <thread>#include <vector>

std::mutex mtx1, mtx2;int shared_data = 0;

void safe_increment(int id) { // scoped_lock captura ambos os mutexes em ordem, evitando deadlock std::scoped_lock lock(mtx1, mtx2); ++shared_data; std::cout << "Thread " << id << " incrementou o valor para " << shared_data << "";}

int main() { std::vector<std::jthread> threads; for (int i = 0; i < 10; ++i) { threads.emplace_back(safe_increment, i); } return 0;}


Para cenários de sincronização mais complexos (por exemplo, produtor-consumidor), são usadas variáveis de condição (std::condition_variable). Elas permitem que as threads aguardem a ocorrência de um evento específico, sem desperdiçar recursos em espera ativa.



3. Operações Atômicas e o Modelo de Memória C++



Operações atômicas (std::atomic) são a base da programação lock-free. Elas garantem que a leitura e a escrita de uma variável sejam executadas como uma única ação indivisível. No entanto, em um nível avançado, é importante entender o modelo de memória C++ e os níveis de ordenação (memory ordering).



Principais níveis de memory ordering:

  • memory_order_relaxed — garantias mínimas, apenas atomicidade
  • memory_order_acquire — garante que todas as leituras e escritas subsequentes vejam os resultados das operações anteriores
  • memory_order_release — garante que todas as operações anteriores sejam visíveis após esta escrita
  • memory_order_acq_rel — combinação de acquire e release

Blogs

Book Recommendations