サーチ…


前書き

プロミスと先物は、あるスレッドから別のスレッドに1つのオブジェクトをフェリーするために使用されます。

std::promiseオブジェクトは、結果を生成するスレッドによって設定されます。

std::futureオブジェクトを使用して値を取得したり、値が使用可能かどうかを調べたり、値が使用可能になるまで実行を停止したりすることができます。

std :: futureとstd :: promise

次の例では、別のスレッドによって使用される約束を設定します。

    {
        auto promise = std::promise<std::string>();
        
        auto producer = std::thread([&]
        {
            promise.set_value("Hello World");
        });
        
        auto future = promise.get_future();
        
        auto consumer = std::thread([&]
        {
            std::cout << future.get();
        });
        
        producer.join();
        consumer.join();
}

遅延非同期の例

このコードはstd::asyncバージョンを実装しますが、 asyncdeferred起動ポリシーで常に呼び出されたかのように動作します。この関数には、 asyncの特別なfuture動作もありません。返還されたfutureは価値を得ることなく破壊される可能futureがあります。

template<typename F>
auto async_deferred(F&& func) -> std::future<decltype(func())>
{
    using result_type = decltype(func());

    auto promise = std::promise<result_type>();
    auto future  = promise.get_future();

    std::thread(std::bind([=](std::promise<result_type>& promise)
    {
        try
        {
            promise.set_value(func()); 
            // Note: Will not work with std::promise<void>. Needs some meta-template programming which is out of scope for this example.
        }
        catch(...)
        {
            promise.set_exception(std::current_exception());
        }
    }, std::move(promise))).detach();

    return future;
}

std :: packaged_taskとstd :: future

std::packaged_taskは、戻り値の型に対して関数とそれに関連する約束を束ねます:

template<typename F>
auto async_deferred(F&& func) -> std::future<decltype(func())>
{
    auto task   = std::packaged_task<decltype(func())()>(std::forward<F>(func));
    auto future = task.get_future();

    std::thread(std::move(task)).detach();

    return std::move(future);
}

スレッドはすぐに実行されます。私たちはそれを切り離すことも、スコープの終わりに参加させることもできます。 std :: threadの関数呼び出しが終了すると、結果は準備完了です。

これは、 std::asyncとは若干異なりstd::future返されるstd::futureは、スレッドが終了するまで実際にブロックされます。

std :: future_errorとstd :: future_errc

std :: promiseおよびstd :: futureの制約が満たされない場合、std :: future_error型の例外がスローされます。

例外内のエラーコードメンバーの型はstd :: future_errcで、値はいくつかのテストケースとともに以下のとおりです。

enum class future_errc {
    broken_promise             = /* the task is no longer shared */,
    future_already_retrieved   = /* the answer was already retrieved */,
    promise_already_satisfied  = /* the answer was stored already */,
    no_state                   = /* access to a promise in non-shared state */
};

不活発な約束:

int test()
{
    std::promise<int> pr;
    return 0; // returns ok
}

アクティブな約束、未使用:

  int test()
    {
        std::promise<int> pr;
        auto fut = pr.get_future(); //blocks indefinitely!
        return 0; 
    }

二重検索:

int test()
{
    std::promise<int> pr;
    auto fut1 = pr.get_future();

    try{
        auto fut2 = pr.get_future();    //   second attempt to get future
        return 0;
    }
    catch(const std::future_error& e)
    {
        cout << e.what() << endl;       //   Error: "The future has already been retrieved from the promise or packaged_task."
        return -1;
    }
    return fut2.get();
}

std :: promise値を2回設定する:

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();
    try{
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
        pr2.set_value(10);  // second attempt to set promise throws exception
    }
    catch(const std::future_error& e)
    {
        cout << e.what() << endl;       //   Error: "The state of the promise has already been set."
        return -1;
    }
    return fut.get();
}

std :: futureとstd :: async

次の単純な並列マージソートの例では、複数の並列merge_sortタスクを起動するためにstd::asyncが使用されています。 std::futureは、結果を待ってそれらを同期するために使用されます:

#include <iostream>
using namespace std;


void merge(int low,int mid,int high, vector<int>&num)
{
    vector<int> copy(num.size());
    int h,i,j,k;
    h=low;
    i=low;
    j=mid+1;
    
    while((h<=mid)&&(j<=high))
    {
        if(num[h]<=num[j])
        {
            copy[i]=num[h];
            h++;
        }
        else
        {
            copy[i]=num[j];
            j++;
        }
        i++;
    }
    if(h>mid)
    {
        for(k=j;k<=high;k++)
        {
            copy[i]=num[k];
            i++;
        }
    }
    else
    {
        for(k=h;k<=mid;k++)
        {
            copy[i]=num[k];
            i++;
        }
    }
    for(k=low;k<=high;k++)
        swap(num[k],copy[k]);
}


void merge_sort(int low,int high,vector<int>& num)
{
    int mid;
    if(low<high)
    {
        mid = low + (high-low)/2;
        auto future1    =  std::async(std::launch::deferred,[&]()
                                      {
                                        merge_sort(low,mid,num);
                                      });
        auto future2    =  std::async(std::launch::deferred, [&]()
                                       {
                                          merge_sort(mid+1,high,num) ;
                                       });
        
        future1.get();
        future2.get();
        merge(low,mid,high,num);
    }
}

注:この例では、 std::asyncはポリシーstd::launch_deferredます。これは、すべての呼び出しで新しいスレッドが作成されないようにするためです。この例の場合、 std::async呼び出しは順不同で行われ、 std::future::get()呼び出しで同期します。

std::launch_asyncは、すべての呼び出しで新しいスレッドを作成します。

デフォルトのポリシーはstd::launch::deferred| std::launch::asyncは、実装が新しいスレッドを作成するためのポリシーを決定することを意味します。

非同期操作クラス

  • std :: async:非同期操作を実行します。
  • std :: future:非同期操作の結果へのアクセスを提供します。
  • std :: promise:非同期操作の結果をパッケージ化します。
  • std :: packaged_task:戻り値の型に対して関数とそれに関連する約束を束ねます。


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow