Threads em C++ – Parte 5: Async

por Fabio A. Mazzarino

std::async é uma função que constrói um std::future baseado em uma função, sem a necessidade de um std::promise, simplificando a utilização de processos assíncronos.

Vamos começar com um exemplo bem simples:


#include <future>
#include <iostream>
#include <list>

int main() {
    int n = 10;
    std::cout << "Generating first " << n << " prime numbers..." << std::endl;
    auto listprimes = std::async(
        std::launch::async,
        [](unsigned n) {
            std::list<unsigned> primes;
            for (int num = 2; primes.size() != n; num++) {
                int div;
                for (div = 2; div < num; div++)
                    if (num % div == 0)
                        break;
                if (div == num)
                    primes.push_back(num);
            }
            return primes;
        },
        n
    );
    auto primes = listprimes.get();

    std::cout << "Primes generated: " << std::endl;
    for (auto it = primes.begin(); it != primes.end(); it++) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
}

O código acima calcula os n primeiros números primos. A função lambda faz o cálculo enquanto que a função main aguarda o processamento. A saída será a seguinte:

Generating first 10 prime numbers...
Primes generated:
2 3 5 7 11 13 17 19 23 29

Opcionalmente a função std::async aceita um parâmetro que define o tempo de execução da função. São duas opções para o tempo de execução:

  1. std::launch::async – cria uma std::thread assíncrona com base na função fornecida e executa o método std::thread::join() logo após a chamada do método std::future::get().
  2. std::launch::deferred – aguarda a chamada do método std::future::wait() ou std::future::get() para executar a função.

É possível ainda fazer um ou binário entre as duas opções (std::launch::async | std::launch::deferred), e o compilador irá selecionar qual o método mais apropriado conforme a implementação da biblioteca.

Um teste para ilustrar os dois casos.



#include <future>
#include <iostream>
#include <thread>
void countdown(unsigned id, unsigned n) {
    for(; n--; ) {
        std::cout << id << "." << n << " " << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));

    }
}
int main() {
    int n = 4;
    std::cout << "Countdown from " << n << " ..." << std::endl;
    auto async1 = std::async(std::launch::async, countdown, 1, n);
    auto async2 = std::async(std::launch::async, countdown, 2, n);
    std::cout << "Waiting for countdown()...";
    async1.wait();
    async2.wait();
    std::cout << "Countdown done" << std::endl << std::endl;

    std::cout << "Countdown from " << n << " ..." << std::endl;
    auto deferred3 = std::async(std::launch::deferred, countdown, 3, n);
    auto deferred4 = std::async(std::launch::deferred, countdown, 4, n);
    std::cout << "Waiting for countdown()...";
    deferred3.wait();
    deferred4.wait();
    std::cout << "Countdown done" << std::endl;
}

São quatro contagens regressivas rodando em paralelo duas a duas. As duas primeiras são executadas utilizando std::launch::async, sendo executadas em paralelo. Já as duas últimas são executadas utilizando std::launch::deferred, e são executadas sequencialmente conforme deferred3.wait() e deferred4.wait() são chamadas. Confira a saída:

Countdown from 4 ...
Waiting for countdown()...1.3
2.3
1.2
2.2
1.1
2.1
2.0
1.0
Countdown done

Countdown from 4 ...
Waiting for countdown()...3.3
3.2
3.1
3.0
4.3
4.2
4.1
4.0
Countdown done