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++: Nível Avançado

\n\n

Aplicações modernas exigem desempenho máximo, e o multithreading em C++ é uma das ferramentas-chave para alcançá-lo. No entanto, mecanismos básicos (std::thread, std::mutex) frequentemente levam a bloqueios, condições de corrida e redução de eficiência. Neste artigo, exploraremos técnicas avançadas de trabalho com concorrência em C++: desde operações atômicas até estruturas de dados lock-free e tarefas assíncronas.

\n\n

1. Operações Atômicas e Memory Ordering

\n\n

Mutexes clássicos são um mecanismo pesado. Para contadores ou flags simples, é melhor usar std::atomic. Ele garante atomicidade de leitura/escrita sem bloqueios. No entanto, em nível avançado, é importante entender o memory ordering — o modelo de memória do C++ que define a visibilidade de alterações entre threads.

\n\n

Principais ordens de memória:

\n
    \n
  • memory_order_relaxed — garantias mínimas, apenas atomicidade.
  • \n
  • memory_order_consume — raramente usado, depende dos dados.
  • \n
  • memory_order_acquire — todas as leituras/escritas subsequentes não podem ser reordenadas antes desta operação.
  • \n
  • memory_order_release — todas as escritas anteriores tornam-se visíveis para outros threads após esta operação.
  • \n
  • memory_order_acq_rel — combinação de acquire e release.
  • \n
  • memory_order_seq_cst — o mais rigoroso, consistência sequencial global (padrão).
  • \n
\n\n

Exemplo de uso:

\n
#include <atomic>\n#include <thread>\n#include <cassert>\n\nstd::atomic<bool> ready{false};\nint data = 0;\n\nvoid producer() {\n    data = 42;                                   // escrita dos dados\n    ready.store(true, std::memory_order_release); // publica a flag\n}\n\nvoid consumer() {\n    while (!ready.load(std::memory_order_acquire)) // aguarda a flag\n        ;\n    assert(data == 42); // garante que veremos data == 42\n}\n\nint main() {\n    std::thread t1(producer);\n    std::thread t2(consumer);\n    t1.join();\n    t2.join();\n    return 0;\n}
\n\n

Aqui, release e acquire garantem que a escrita de data ocorra antes da definição da flag, e a leitura, depois. Isso é mais rápido que um mutex.

\n\n

2. Estruturas de Dados Lock-Free

\n\n

Algoritmos lock-free permitem evitar bloqueios usando operações atômicas e CAS (Compare-And-Swap). Um exemplo clássico é uma pilha lock-free baseada em lista encadeada simples.

\n\n

Implementação de uma pilha lock-free:

\n
#include <atomic>\n#include <memory>\n\ntemplate<typename T>\nclass LockFreeStack {\nprivate:\n    struct Node {\n        T data;\n        Node* next;\n        Node(const T& val) : data(val), next(nullptr) {}\n    };\n    std::atomic<Node*> head{nullptr};\n\npublic:\n    void push(const T& val) {\n        Node* new_node = new Node(val);\n        new_node->next = head.load(std::memory_order_relaxed);\n        while (!head.compare_exchange_weak(\n            new_node->next, new_node,\n            std::memory_order_release,\n            std::memory_order_relaxed)) {\n            // loop até o CAS ter sucesso\n        }\n    }\n\n    bool pop(T& result) {\n        Node* old_head = head.load(std::memory_order_relaxed);\n        while (old_head != nullptr &&\n               !head.compare_exchange_weak(\n                   old_head, old_head->next,\n                   std::memory_order_acquire,\n                   std::memory_order_relaxed)) {\n            // aguarda\n        }\n        if (old_head == nullptr) return false;\n        result = old_head->data;\n        // Atenção: em código real, é necessário um coletor de lixo ou hazard pointers\n        delete old_head;\n        return true;\n    }\n};
\n\n

Observações importantes:

\n
    \n

Blogs

Book Recommendations