424

私はC++ 11のstd::threadstd::asyncおよびstd::futureコンポーネント(たとえば、この回答を参照)にかなり精通しています。これらは簡単です。

std::promiseしかし、それが何なのか、何をするのか、どのような状況で最もよく使われるのか、私にはよくわかりません。標準ドキュメント自体には、そのクラスの概要を超える多くの情報は含まれておらず、std::threadも含まれていません。

誰かstd::promiseが必要な状況と、それが最も慣用的な解決策である状況の簡単で簡潔な例を教えてください。

4

9 に答える 9

556

私は今、状況を少しよく理解しています(ここでの回答により、少なからずです!)、私は自分自身の記事を少し追加すると思いました.


C++11 には、関連はあるものの、2 つの異なる概念があります。非同期計算 (別の場所で呼び出される関数) と、同時実行 (スレッド、同時に動作するもの) です。この 2 つは、やや直交する概念です。スレッドが実行コンテキストであるのに対し、非同期計算は関数呼び出しの別のフレーバーです。スレッドはそれ自体が有用ですが、この議論の目的のために、スレッドを実装の詳細として扱います。


非同期計算には抽象化の階層があります。例として、いくつかの引数を取る関数があるとします。

int foo(double, char, bool);

まずstd::future<T>、 type の将来の値を表す template がありますT。値はメンバー関数を介して取得できget()、結果を待機することでプログラムを効果的に同期します。wait_for()あるいは、結果がすでに利用可能かどうかを調べるために使用できるfuture をサポートします。フューチャーは、通常の戻り値の型を非同期でドロップインで置き換えるものと考える必要があります。この例の関数では、std::future<int>.

次に、最上位レベルから最下位レベルまでの階層に進みます。

  1. std::async: 非同期計算を実行する最も便利で簡単な方法asyncは、一致する Future をすぐに返す関数テンプレートを使用することです。

    auto fut = std::async(foo, 1.5, 'x', false);  // is a std::future<int>
    

    詳細についてはほとんど制御できません。get()特に、関数が同時に実行されるのか、シリアルに実行されるのか、それとも他の黒魔術によって実行されるのかさえわかりません。ただし、結果は必要に応じて簡単に取得できます。

    auto res = fut.get();  // is an int
    
  2. のようなものを実装する方法を検討できますが、制御asyncできる方法で行います。たとえば、関数を別のスレッドで実行するように要求する場合があります。クラスを使用して別のスレッドを提供できることは既にわかっていますstd::thread

    抽象化の次の下位レベルは、まさにそれを行いますstd::packaged_task。これは、関数をラップし、関数の戻り値に未来を提供するテンプレートですが、オブジェクト自体は呼び出し可能であり、呼び出しはユーザーの裁量に任されています。次のように設定できます。

    std::packaged_task<int(double, char, bool)> tsk(foo);
    
    auto fut = tsk.get_future();    // is a std::future<int>
    

    タスクを呼び出して呼び出しが完了すると、未来の準備が整います。これは、別のスレッドの理想的な仕事です。タスクをスレッドに移動することを確認する必要があります。

    std::thread thr(std::move(tsk), 1.5, 'x', false);
    

    スレッドはすぐに実行を開始します。それか、スコープの最後か、またはいつでも (たとえば、実際には標準ライブラリにあるはずの Anthony Williams のラッパーを使用して)detach持つことができます。ただし、使用の詳細はここでは関係ありません。最終的には必ず参加または切り離してください。重要なのは、関数呼び出しが終了するたびに、結果の準備が整っているということです。joinscoped_threadstd::threadthr

    auto res = fut.get();  // as before
    
  3. これで、最も低いレベルに到達しました。パッケージ化されたタスクをどのように実装しますか? ここで のstd::promise出番です。約束は、未来とのコミュニケーションの構成要素です。主な手順は次のとおりです。

    • 呼び出しスレッドは約束をします。

    • 呼び出し元のスレッドは、promise から future を取得します。

    • promise は、関数の引数と共に、別のスレッドに移動されます。

    • 新しいスレッドは関数を実行し、約束を果たします。

    • 元のスレッドが結果を取得します。

    例として、これが私たち自身の「パッケージ化されたタスク」です。

    template <typename> class my_task;
    
    template <typename R, typename ...Args>
    class my_task<R(Args...)>
    {
        std::function<R(Args...)> fn;
        std::promise<R> pr;             // the promise of the result
    public:
        template <typename ...Ts>
        explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { }
    
        template <typename ...Ts>
        void operator()(Ts &&... ts)
        {
            pr.set_value(fn(std::forward<Ts>(ts)...));  // fulfill the promise
        }
    
        std::future<R> get_future() { return pr.get_future(); }
    
        // disable copy, default move
    };
    

    このテンプレートの使用法は、基本的に の使用法と同じですstd::packaged_task。タスク全体を移動すると、promise が移動されることに注意してください。よりアドホックな状況では、promise オブジェクトを明示的に新しいスレッドに移動し、それをスレッド関数の関数引数にすることもできますが、上記のようなタスク ラッパーは、より柔軟で邪魔にならないソリューションのようです。


例外を作る

Promise は、例外と密接に関連しています。promise のインターフェースだけでは、その状態を完全に伝えるには不十分であるため、promise に対する操作が意味をなさない場合は常に例外がスローされます。すべての例外は、std::future_errorから派生したタイプstd::logic_errorです。まず、いくつかの制約について説明します。

  • デフォルトで構築された promise は非アクティブです。非アクティブなプロミスは、結果を伴わずに死ぬ可能性があります。

  • を介してフューチャを取得すると、Promise がアクティブになりget_future()ます。ただし、得られる未来は1つだけ!

  • 将来が消費される場合、Promise は、その有効期間が終了する前に、経由で満たされるset_value()か、経由で例外が設定されている必要があります。set_exception()満足のいく約束は、結果を伴わずに死ぬget()可能性があり、将来的に利用可能になります。例外のある promise は、future の呼び出し時に格納された例外を発生さget()せます。promise が値も例外もなく終了した場合get()、future を呼び出すと「壊れた promise」例外が発生します。

これらのさまざまな例外的な動作を実証するための小さなテスト シリーズを次に示します。まず、ハーネス:

#include <iostream>
#include <future>
#include <exception>
#include <stdexcept>

int test();

int main()
{
    try
    {
        return test();
    }
    catch (std::future_error const & e)
    {
        std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl;
    }
    catch (std::exception const & e)
    {
        std::cout << "Standard exception: " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Unknown exception." << std::endl;
    }
}

それでは、テストに進みます。

ケース 1: 非アクティブな約束

int test()
{
    std::promise<int> pr;
    return 0;
}
// fine, no problems

ケース 2: 有効なプロミス、未使用

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();
    return 0;
}
// fine, no problems; fut.get() would block indefinitely

ケース 3: 先物が多すぎる

int test()
{
    std::promise<int> pr;
    auto fut1 = pr.get_future();
    auto fut2 = pr.get_future();  //   Error: "Future already retrieved"
    return 0;
}

ケース 4: 満足された約束

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

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
    }

    return fut.get();
}
// Fine, returns "10".

ケース 5: 満足度が高すぎる

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

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
        pr2.set_value(10);  // Error: "Promise already satisfied"
    }

    return fut.get();
}

またはのいずれかが複数ある場合、同じ例外がスローされます。set_valueset_exception

ケース 6: 例外

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

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo")));
    }

    return fut.get();
}
// throws the runtime_error exception

ケース 7: 約束を破った

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

    {
        std::promise<int> pr2(std::move(pr));
    }   // Error: "broken promise"

    return fut.get();
}
于 2012-09-08T23:10:05.543 に答える
207

[futures.state] の言葉でstd::futureは、a は非同期の戻りオブジェクト(「共有状態から結果を読み取るオブジェクト」) であり、astd::promise非同期プロバイダー(「結果を共有状態に提供するオブジェクト」) です。 promise は結果を設定するものであり、関連付けられた未来から結果を取得できます。

非同期プロバイダーは、future が参照する共有状態を最初に作成するものです。std::promiseは非同期プロバイダーの 1 つのタイプであり、std::packaged_task別のタイプであり、内部の詳細は別のタイプですstd::async。それらのそれぞれが共有状態を作成し、std::futureその状態を共有する を提供し、状態を準備できます。

std::asyncは、非同期結果オブジェクトを提供し、タスクの完了時に非同期プロバイダーを作成して共有状態を準備できるようにする高レベルの便利なユーティリティです。std::packaged_task(orstd::bindと a std::promise) と aでエミュレートできますが、std::threadより安全で使いやすいstd::asyncです。

std::promiseは、非同期の結果を未来に渡したいが、結果を準備するコードを に渡すのに適した単一の関数にまとめることができない場合のために、少し低レベルですstd::async。たとえば、複数promiseの および関連する の配列があり、future複数の計算を実行して各 promise に結果を設定する単一のスレッドがあるとします。async単一の結果のみを返すことができます。複数を返すには、async複数回呼び出す必要があり、リソースを浪費する可能性があります。

于 2012-06-13T17:59:00.707 に答える
37

Bartosz Milewski が優れた記事を提供しています。

C++ は先物の実装を一連の小さなブロックに分割します

std::promise はこれらの部分の 1 つです。

promise は、関数を実行するスレッドから、関数の future をキャッシュするスレッドに戻り値 (または例外) を渡す手段です。

...

future は、promise チャネルの受信側で構築された同期オブジェクトです。

したがって、future を使用する場合は、非同期処理の結果を取得するために使用する promise になります。

ページの例は次のとおりです。

promise<int> intPromise;
future<int> intFuture = intPromise.get_future();
std::thread t(asyncFun, std::move(intPromise));
// do some other stuff
int result = intFuture.get(); // may throw MyException
于 2012-06-12T20:34:59.097 に答える
32

std::promise大まかな概算では、 a のもう一方の端と見なすことができますstd::future(これはfalseですが、説明のために、あたかもそうであるかのように考えることができます)。通信チャネルのコンシューマ エンドは、 a を使用しstd::futureて共有状態からデータを消費し、プロデューサー スレッドは a を使用std::promiseして共有状態に書き込みます。

于 2012-06-12T20:30:27.180 に答える
13

std::promise非同期関数から返される情報のチャネルまたは経路です。std::futureで運ばれる戻り値の準備が整うまで呼び出し元を待機させる同期メカニズムstd::promiseです (つまり、その値が関数内で設定されます)。

于 2012-06-12T20:42:10.683 に答える
0

http://www.cplusplus.com/reference/future/promise/

一文の説明: furture::get() は promse::set_value() を永遠に待ちます。

void print_int(std::future<int>& fut) {
    int x = fut.get(); // future would wait prom.set_value forever
    std::cout << "value: " << x << '\n';
}

int main()
{
    std::promise<int> prom;                      // create promise

    std::future<int> fut = prom.get_future();    // engagement with future

    std::thread th1(print_int, std::ref(fut));  // send future to new thread

    prom.set_value(10);                         // fulfill promise
                                                 // (synchronizes with getting the future)
    th1.join();
    return 0;
}
于 2019-06-06T09:15:57.327 に答える