Utilizando futures para programação assíncrona em C++11

O cabeçalho <future>, introduzido no C++11, fornece mecanismos para transportar resultados assíncronos. Ele permite que tarefas concorrentes ou assíncronas transmitam valores de retorno ou exceções de forma segura, eliminando a necessidade de implementar manualmente locks, variáveis de condição ou callbacks.

Componentes Principais

Classe/Função Função Complemento
std::async Inicia uma tarefa assíncrona Retorna std::future
std::future<T> Recebe um resultado assíncrono único async, packaged_task, promise
std::promise<T> Define um resultado manualmente Compartilha estado com future
std::shared_future<T> Permite compartilhar um mesmo resultado entre múltiplas threads Pode ser copiado
std::packaged_task<R(Args...)> Encapsula um objeto invocável como uma tarefa assíncrona Compartilha estado com future

Exemplo Básico

1. Assincronicidade com std::async

#include <future>
#include <iostream>

int calcular() { return 42; }

int main() {
    std::future<int> resultado = std::async(std::launch::async, calcular);
    std::cout << resultado.get();   // Bloqueia até que o resultado esteja disponível
}

2. Enviando resultados manualmente com promise/future

std::promise<int> promessa;
std::future<int>  futuro = promessa.get_future();

std::thread t([&promessa]{
    promessa.set_value(100);      // Define o resultado na thread
});

std::cout << futuro.get();       // A thread principal obtém 100
t.join();

Quando Utilizar

Cenário Uso Recomendado
Computação simples em segundo plano std::async
Tarefas em pool de threads packaged_task ou promise
Necessidade de múltiplos consumidores shared_future

Cuidados Importantes

  • future.get() pode ser chamado apenas uma vez; para múltiplas leituras, utilize shared_future.
  • Destruir um promise sem definir um valor resulta em std::future_error.
  • Por padrão, std::async pode executar de forma síncrona (dependente da implementação); especifique std::launch::async para garantir execução concorrente.

Resumo

<future> permite "enviar tarefas" de forma similar a "enviar uma encomenda", recebendo os resultados de maneira segura sem a necessidade de locks ou variáveis de condição.

Função async

#include <iostream>
#include <future>
#include <thread>

int somar(int a, int b)
{
    std::cout << "Executando somar!\n";
    return a + b;
}

int main()
{
    // Estratégia std::launch::async: cria uma nova thread para executar a função. O resultado é obtido via future.
    // Estratégia std::launch::deferred: execução adiada (síncrona), a tarefa é executada ao chamar get().
    std::future<int> resultado = std::async(std::launch::deferred, somar, 11, 22); // Chamada assíncrona não-bloqueante
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "--------------------------\n";
    // std::future<int>::get() é usado para obter o resultado da tarefa assíncrona. Bloqueia se o resultado não estiver pronto.
    std::cout << resultado.get() << std::endl;
    return 0;
}

// Saída com deferred:
// --------------------------
// Executando somar!
// 33

// Saída com async:
// Executando somar!
// --------------------------
// 33

Diferenças de comportamento entre std::launch::deferred e std::launch::async:

Estratégia Thread Momento da Execução Ordem da Saída
deferred Thread atual Somente na primeira chamada a get() ------------------Executando somar!
async Nova thread Imediatamente, de forma concorrente Executando somar!------------------

Resumo

  • deferred = Sincronia adiada (execução preguiçosa);
  • async = Assincronia real (execução imediata em segundo plano).

Promise

#include <iostream>
#include <future>
#include <thread>
#include <memory>

int somar(int a, int b)
{
    std::cout << "Executando somar!\n";
    return a + b;
}

int main()
{
    //   1. Instanciar um objeto promise com o tipo de resultado esperado.
    std::promise<int> promessa;
    //   2. Obter o objeto future associado a essa promise.
    std::future<int> futuro = promessa.get_future();
    //   3. Em qualquer ponto, definir o valor na promise; ele poderá ser recuperado via future.
    std::thread worker([&promessa](){
        int resultado = somar(11, 22);
        promessa.set_value(resultado);
    });

    std::cout << futuro.get() << std::endl;
    worker.join();
    
    return 0;
}

Fluxo Chave (correspondendo aos comentários)

Passo Código Significado
1️⃣ Criar a promessa promise<int> promessa; Preparar uma "caixa" para o resultado
2️⃣ Obter o cupom future<int> futuro = promessa.get_future(); A thread principal obtém o "cupom de retirada"
3️⃣ Produção assíncrona thread worker([&promessa]{...}) A thread de trabalho calcula e coloca o resultado na caixa
4️⃣ Bloqueio para retirada futuro.get() A thread principal bloqueia de forma segura até que o resultado esteja pronto
5️⃣ Limpar recursos worker.join() Esperar que a thread de trabalho termine

Erros Comuns

  1. Esquecer de chamar join() → A thread ainda está em execução quando o programa encerra, causando terminate.
  2. Chamar get() múltiplas vezesfuture só permite uma chamada a get(); para múltiplas leituras, utilize shared_future.
  3. Propagação de exceções → Se a thread de trabalho lançar uma exceção, get() na thread principal a relançará.

Resumo

promise é responsável por "colocar", future por "retirar";
A combinação promise/future permite transferir resultados assíncronos para a thread principal com segurança e sem locks.

packaged_task

#include <iostream>
#include <future>
#include <thread>
#include <memory>

int multiplicar(int x, int y) {
    std::cout << "Executando multiplicar!\n";
    return x * y;
}

int main()
{
    //1. Encapsular a tarefa
    auto tarefa = std::make_shared<std::packaged_task<int(int, int)>>(multiplicar);

    //2. Obter o objeto future associado à tarefa
    std::future<int> futuro = tarefa->get_future();

    std::thread worker([tarefa](){
        (*tarefa)(5, 7);
    });

    //3. Obter o resultado
    std::cout << futuro.get() << std::endl;
    worker.join();

    return 0;
}
Passo Código Função
1️⃣ Encapsular a tarefa make_shared<packaged_task<int(int,int)>> Transformar uma função comum em uma tarefa executável de forma assíncrona
2️⃣ Obter o resultado future<int> futuro = tarefa->get_future(); Obter o "cupom de retirada"
3️⃣ Execução assíncrona thread worker([tarefa]{ (*tarefa)(5,7); }) Invocar efetivamente a tarefa dentro de uma thread
4️⃣ Bloqueio para retirada futuro.get() A thread principal bloqueia de forma segura até que o resultado esteja pronto

Resultado da Execução

Executando multiplicar!
35

Pontos de Atenção

  • Só é possível chamar get() uma vez; para múltiplas leituras, utilize std::shared_future.
  • Exceção na tarefafuturo.get() relançará a exceção.
  • Esquecer de chamar worker.join() → A thread ainda está ativa ao encerrar o programa, resultando em std::terminate.

Resumo

packaged_task transforma um "objeto invocável" em uma "tarefa com retorno, executável de forma assíncrona". Combinado com future, implementa a transferência segura de resultados entre threads sem a necsesidade de locks.

Tags: C++ C++11 future async promise

Publicado em 6-3 05:37 por Thomas