Pular para conteúdo

Concorrência em C++

C++ combina threads, mutexes, variáveis de condição, futures, atomics e modelo formal de memória. Uma corrida de dados em memória comum causa comportamento indefinido.

Vida da thread

std::jthread é uma thread C++20 que solicita parada e faz join na destruição. Cancelamento cooperativo exige observar o stop token.

void consume(std::stop_token stop, work_queue& queue) {
    while (!stop.stop_requested()) {
        if (auto item = queue.try_pop()) {
            process(*item);
        }
    }
}

std::jthread worker{consume, std::ref(queue)};

O estado referenciado deve sobreviver aos acessos. Join não corrige referência já dangling.

Invariante protegido por mutex

class counter {
public:
    void increment() {
        std::lock_guard guard{mutex_};
        ++value_;
    }

    [[nodiscard]] std::uint64_t value() const {
        std::lock_guard guard{mutex_};
        return value_;
    }

private:
    mutable std::mutex mutex_;
    std::uint64_t value_{};
};

Mutex e estado têm um proprietário e todos os acessos seguem o protocolo. Várias chamadas bloqueadas individualmente não formam operação composta atômica.

Variáveis de condição

Espere com predicado por causa de despertares espúrios e concorrência:

condition.wait(lock, [&] { return closed || !queue.empty(); });

O predicado lê estado protegido pelo mesmo mutex. A notificação não é evento armazenado.

Atomics e ordenação

std::atomic<T> oferece operações atômicas. Ordem sequencialmente consistente é mais simples. Ordens fracas exigem prova ligando sincronização, vida e todos os acessos não atômicos. Atomicidade de uma variável não cobre invariante multivariável. Lock-free não significa wait-free, mais rápido nem simples.

volatile não sincroniza

Em C++ portável, volatile não torna operações atômicas nem cria ordenação entre threads. Tem usos especializados conforme contratos da plataforma.

Computação paralela

Ajuda quando o trabalho é grande e independente. Fração sequencial, escalonamento, contenção, false sharing, cache, largura de banda e oversubscription limitam a aceleração.

Exercícios

  1. Explique a corrida em duas threads com ++ordinary_counter.
  2. Projete uma fila bloqueante limitada.
  3. Explique por que shared pointer protege posse, não o estado apontado.