haraduka's diary

やる気が欲しい

std::future, promise, async, packaged_task…使ったことなし。

C++、人が使ってるのは見たことあっても実際に使ったことのないもの多すぎですね。
今回はstd::future, std::promise, std::async, std::packaged_taskについて書きます。

std::futureは、非同期操作の結果にアクセスするためのメカニズムを提供するもの。単体では使いません。多分。
std::promiseは、std:futureとstd::threadと一緒に用いて非同期操作を実現するもの。非同期に取得した値を格納するのに使う。
std::asyncは、非同期に関数を実行し、その結果を出力するstd::future。
std::packaged_taskは、std::asyncと似ているが少し違う。非同期実行の結果をstd::futureに書き込むもの。

ごちゃごちゃ言ってないでコードを見てみましょう。

std::promiseを使う例

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

int main() {
    std::promise<int> prms;
    std::future<int> ftr = prms.get_future();
    std::thread th([&]() {
        int sum = 0;
        for(int i=1; i<=10; i++){
            sum += i;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        prms.set_value(sum);
    });
    std::cout << ftr.get() << std::endl;
    th.join();
    return 0;
}

まず、格納したい値の型をテンプレート引数としたpromiseを作成します。そして、そのpromiseが将来的に返すであろう、futureを取得しておきます。
非同期で勝手にスレッドを回しておきます。futureのgetメソッドを呼び出した時には、futureに値が入ってくるまでブロックします。これだけです。
スレッドはとりあえず回しておいて、値が欲しくなったらfuture::getを呼びだせばいいわけです。便利。
ちなみに、future::getは一度しか呼び出せない。もし何度も呼び出したいのであればfutureをshared_futureにするべし。


std::asyncを使う例

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

int main()
{
    std::future<int> ftr = std::async(std::launch::async,
        [&]() {
            int sum = 0;
            for(int i=1; i<=10; i++){
                sum += i;
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
            return sum;
        });
    std::cout << ftr.get() << std::endl;
    return 0;
}

前のpromiseを使った例の簡単バージョンみたいになる。前ではpromiseを用意して、threadでpromiseを設定してfutureからgetする、という感じだった。
しかしasyncを使えば、returnした値をfutureの中にそのまま格納してくれるため、スレッドを作る必要も、promiseを用意する必要もない。簡単!
asyncにはpolicyというのがあって、std::asyncの第一引数の値に

  • std::launch::asyncを指定すると別スレッドで実行
  • std::launch::deferredを指定すると遅延評価
  • 何も指定しないと上記どちらになるかは実装依存

となる。

std::packaged_taskを使った例

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

int main()
{
    std::packaged_task<int()> ptask([]() {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            sum += i;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        return sum;
    });

    std::future<int> ftr = ptask.get_future();
    std::thread th(std::move(ptask));
    std::cout << ftr.get() << std::endl;
    th.join();
    return 0;
}

なんとも微妙…。packaged_taskに非同期で結果が欲しいタスクを登録する。そしてそのfutureを取っておく。packaged_taskをthreadで実行しておいて、future::getで値が入るまでブロッキング
あまり用途が理解できないが、汎用性がある?w

まぁいいや