Threads em C++ – Parte 4: Introdução às Promises

por Fabio A. Mazzarino

Promises fazem parte das novas formas de gerenciar produção e consumo de dados entre threads, basicamente sincronizando a execução de uma thread de geração ou recuperação de dados, com a thread que consome esses mesmos dados.

Vamos começar com uma analogia. Felipe está esperando uma correspondência que Pedro vai trazer; ao invés de ficar regularmente verificando regularmente a caixa de correio, Felipe prefere colocar uma sineta na caixa de correio enquanto dorme ao seu lado. Quando Pedro conseguir trazer a carta a campainha vai tocar e Felipe pode acordar para poder abrir a correspondência.

Pedro é uma Promise, enquanto que Felipe é um Future. Em C++ são Promise e Future são classes que são utilizadas para se comunicar entre si e indicar que um determinado dado está pronto para ser consumido. Um exemplo bem básico:

#include <iostream>
#include <future>
#include <thread>
int main() {
    std::promise<unsigned long long> provider;
    std::future<unsigned long long> consumer = provider.get_future();
    std::thread fat(
        [&provider](unsigned n) {
            unsigned long long r = 1;
            for (int ct = n; ct--; )
                r *= ct + 1;
            std::cout << "Factorial ready" << std::endl;
            provider.set_value(r);
        },
        20
    );
    std::cout << "Waiting for factorial" << std::endl;
    long long r = consumer.get();
    std::cout << "r: " << r << std::endl;
    fat.join();
}

Que entregará a saída:

Waiting for factorial
Factorial ready
r: 2432902008176640000

provider é um objeto std::promise que está associada à uma thread que calcula o fatorial, e consumer é um objeto std::future que consome o fatorial gerado pela thread.

Existe também a classe packaged_task, que basicamente é uma promise que contém sua própria função, simplificando a implementação:

#include <future>
#include <iostream>
int main() {
    std::packaged_task<void(unsigned)> waittimer(
        [](unsigned secs) {
            for (int ct = secs; ct--; )
                std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    );
    std::future<void> wait = waittimer.get_future();
    std::thread waitthread(std::move(waittimer), 5);

    std::cout << "Calculating factorial..." << std::endl;
    unsigned long long fat = 1;
    for (int ct = 10; ct--; )
        fat *= ct + 1;
    std::cout << "Factorial ready: " << fat << std::endl;

    std::cout << "Waiting until 5 secs..." << std::endl;
    wait.get();
    std::cout << "Done!" << std::endl;

    waitthread.join();
}

E a saída será:

Calculating factorial...
Factorial ready: 3628800
Waiting until 5 secs...
Done!

Note a necessidade de usar std::move para evitar a criação de uma nova instância da promise, o que poderia causar problemas.

Parece simples, e é simples, pelo menos pro enquanto, porque as coisas podem ficar mais complexas. Mas para deixar as coisas mais complexas vamos precisar de outros posts.