web-dev-qa-db-ja.com

std :: promiseとは何ですか?

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

しかし、私はstd::promiseが何であるか、それが何をしているのか、そしてどの状況でそれが最もよく使われているのかをよく理解することはできません。標準文書自体には、そのクラスの概要を超えた多くの情報は含まれていません。また、 just :: thread も含まれていません。

誰かがstd::promiseが必要とされ、それが最も慣用的な解決策である状況の簡単で簡潔な例をお願いします。

354
Kerrek SB

[futures.state]の言葉では、std::future非同期戻りオブジェクト( "共有状態から結果を読み取るオブジェクト")であり、std::promise非同期プロバイダーです。 ( "共有状態に結果を提供するオブジェクト")すなわち約束はあなたがset結果を出すことであるので、あなたはget関連からそれを得ることができます未来。

非同期プロバイダは、将来が参照する共有状態を最初に作成するものです。 std::promiseは非同期プロバイダの一種で、std::packaged_taskは別のものです。そしてstd::asyncの内部の詳細は別のものです。それぞれが共有状態を作成し、その状態を共有するstd::futureを提供して、状態を準備することができます。

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

std::promiseは、非同期の結果を将来に渡したい場合のために、もう少し低レベルですが、結果を準備するコードを、std::asyncに渡すのに適した単一の関数にまとめることはできません。たとえば、いくつかのpromisesとそれに関連するfuturesの配列があり、いくつかの計算を実行して各約束に対して結果を設定する単一のスレッドがあるとします。 asyncは単一の結果を返すことのみを許可し、複数を返すにはasyncを数回呼び出す必要があり、これはリソースを浪費する可能性があります。

169
Jonathan Wakely

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


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


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

int foo(double, char, bool);

最初に、テンプレート std::future<T> があります。これはT型の将来の値を表します。値は、結果を待つことでプログラムを効果的に同期するメンバー関数get()を介して取得できます。あるいは、将来はwait_for()をサポートします。これは、結果がすでに利用可能かどうかを調べるために使用できます。先物は、通常の戻り値型の非同期ドロップイン置換と見なされる必要があります。サンプル関数では、std::future<int>が必要です。

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

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

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

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

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

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

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

    タスクを呼び出して呼び出しが完了すると、将来の準備が整います。これは、安全なスレッドの理想的な仕事です。タスクをスレッドにmoveするだけです:

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

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

    auto res = fut.get();  // as before
    
  3. これで最低レベルになりました:パッケージ化されたタスクを実装する方法は?これが std::promise の出番です。約束は未来と通信するための構成要素です。主な手順は次のとおりです。

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

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

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

    • 新しいスレッドは関数を実行し、データを投入して約束を果たします。

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

    例として、非常に独自の「パッケージ化されたタスク」を次に示します。

    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の操作が意味をなさない場合は常に例外がスローされます。すべての例外はstd::future_errorから派生したstd::logic_error型です。まず、いくつかの制約の説明:

  • デフォルトで構築されたプロミスは非アクティブです。非アクティブな約束は結果なしで死ぬ可能性があります。

  • get_future()を介してフューチャーが取得されると、Promiseがアクティブになります。ただし、onefutureのみを取得できます!

  • 将来を消費する場合は、set_value()を介してプロミスを満たすか、ライフタイムが終了する前にset_exception()を介して例外を設定する必要があります。満足した約束は結果なしに死ぬ可能性があり、get()は将来利用可能になります。例外のあるプロミスは、将来のget()の呼び出し時に保存された例外を発生させます。約束が値も例外もなしで死ぬ場合、将来にget()を呼び出すと、「壊れた約束」例外が発生します。

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

#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_valueまたはset_exceptioneitherが複数ある場合、同じ例外がスローされます。

ケース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();
}
468
Kerrek SB

Bartosz Milewski 良い記事を書いています。

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

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

プロミスとは、関数を実行しているスレッドから将来​​の関数に組み込まれているスレッドに戻り値(または例外)を渡すための手段です。

...

将来は、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
32
Paul Rubel

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

std::promiseは、非同期関数から返される情報のチャネルまたは経路です。 std::futureは、std::promiseに含まれる戻り値の準備ができるまで(呼び出し側に値が関数内で設定されることを意味します)、呼び出し側を待機させる同期メカニズムです。

10
kjp

非同期処理には、本当に3つのコアエンティティがあります。 C++ 11は現在それらの2つに焦点を合わせています。

いくつかのロジックを非同期的に実行するために必要なコアとなるものは以下のとおりです。

  1. 「どこか」で実行されるタスク(何らかのファンクタオブジェクトとしてパッケージされたロジック)。
  2. 実際の処理ノード - 提供されたときにそのような関数を実行するスレッド、プロセスなど。基本的なワーカースレッドプールでこれがどのように行われるかについては、 "Command"デザインパターンを見てください。
  3. 結果ハンドル:誰かがその結果を必要とし、それをGETするオブジェクトを必要とします。 OOPおよびその他の理由から、待機または同期はこのハンドルのAPIで実行する必要があります。

C++ 11では、(1)std::promiseで、および(3)std::futureで私が話していることを呼び出します。 std::threadは(2)に対して公的に提供されている唯一のものです。実際のプログラムはスレッドとメモリのリソースを管理する必要があり、小さなタスクごとにスレッドを作成して破棄するのではなく、ほとんどのタスクをスレッドプールで実行する必要があるため、残念です。さらに悪いことに飢餓)。

Herb Sutterと他のC++ 11の脳の信頼関係者によると、Javaのようにstd::executorを追加することを暫定的に計画しています。たぶん私たちはC++ 2014でそれを見るでしょう、しかし私の賭けはC++ 17にもっと似ています(そして彼らがこれらの標準をくぐらせたなら神は私たちを助けます)。

7
Zack Yezek

std::promiseは、promise/futureペアのエンドポイントとして作成され、std::futureget_future()メソッドを使用してstd :: promiseから作成される)はもう1つのエンドポイントです。これは、1つのスレッドがメッセージを介して別のスレッドにデータを提供するため、2つのスレッドが同期するための方法を提供する簡単なワンショット方式です。

一方のスレッドがデータを提供するという約束を作成し、もう一方のスレッドが将来その約束を収集すると考えることができます。このメカニズムは一度だけ使用できます。

約束/将来のメカニズムは、std::promiseset_value()メソッドを使用するスレッドからstd::futureget()を使用するスレッドへの一方向のみです。将来のget()メソッドが複数回呼び出されると、例外が生成されます。

std::promiseを持つスレッドがその約束を満たすためにset_value()を使わなかった場合、2番目のスレッドがstd::futureget()を呼び出して約束を集めると、2番目のスレッドはstd::promiseを持つ最初のスレッドによって約束が満たされるまで待ち状態に入ります。データを送信するためにset_value()メソッドを使用するとき。

提案されているコルーチン 技術仕様書N4663プログラミング言語 - コルーチン用C++拡張 およびVisual Studio 2017 C++コンパイラによるco_awaitのサポートにより、std::futureおよびstd::asyncを使用してコルーチン機能を作成することもできます。 https://stackoverflow.com/a/50753040/146697 にあるstd::futureco_awaitの使用を説明する1つのセクションとしての説明と例を参照してください。

次のコード例、単純なVisual Studio 2013 Windowsコンソールアプリケーションは、いくつかのC++ 11同時実行クラス/テンプレートおよびその他の機能の使用方法を示しています。これは、うまく機能するpromise/futureの使用、何らかのタスクを実行して停止する自律スレッド、およびより多くの同期動作が必要であり、複数の通知が必要なためpromise/futureのペアが機能しない用途を示します。

この例に関する1つの注意点は、さまざまな場所で追加された遅延です。これらの遅延は、std::coutを使用してコンソールに表示されるさまざまなメッセージが明確で、複数のスレッドからのテキストが混ざらないようにするためにのみ追加されました。

main()の最初の部分は、3つの追加スレッドを作成し、std::promisestd::futureを使用してスレッド間でデータを送信することです。おもしろい点は、メインスレッドがメインスレッドからのデータを待って何かをしてから3番目のスレッドT3にデータを送るスレッドT2を起動するところです。メインスレッド.

main()の2番目の部分では、2つのスレッドと1組のキューを作成して、メインスレッドから2つの各スレッドへの複数のメッセージを許可します。 std::promisestd::futureを使うことはできません。これは、promise/futureデュオがワンショットであり、繰り返し使用できないためです。

クラスSync_queueのソースは、StroustrupのThe C++ Programming Language:4th Editionにあります。

// cpp_threads.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <thread>  // std::thread is defined here
#include <future>  // std::future and std::promise defined here

#include <list>    // std::list which we use to build a message queue on.

static std::atomic<int> kount(1);       // this variable is used to provide an identifier for each thread started.

//------------------------------------------------
// create a simple queue to let us send notifications to some of our threads.
// a future and promise are one shot type of notifications.
// we use Sync_queue<> to have a queue between a producer thread and a consumer thread.
// this code taken from chapter 42 section 42.3.4
//   The C++ Programming Language, 4th Edition by Bjarne Stroustrup
//   copyright 2014 by Pearson Education, Inc.
template<typename Ttype>
class Sync_queue {
public:
    void  put(const Ttype &val);
    void  get(Ttype &val);

private:
    std::mutex mtx;                   // mutex used to synchronize queue access
    std::condition_variable cond;     // used for notifications when things are added to queue
    std::list <Ttype> q;              // list that is used as a message queue
};

template<typename Ttype>
void Sync_queue<Ttype>::put(const Ttype &val) {
    std::lock_guard <std::mutex> lck(mtx);
    q.Push_back(val);
    cond.notify_one();
}

template<typename Ttype>
void Sync_queue<Ttype>::get(Ttype &val) {
    std::unique_lock<std::mutex> lck(mtx);
    cond.wait(lck, [this]{return  !q.empty(); });
    val = q.front();
    q.pop_front();
}
//------------------------------------------------


// thread function that starts up and gets its identifier and then
// waits for a promise to be filled by some other thread.
void func(std::promise<int> &jj) {
    int myId = std::atomic_fetch_add(&kount, 1);   // get my identifier
    std::future<int> intFuture(jj.get_future());
    auto ll = intFuture.get();   // wait for the promise attached to the future
    std::cout << "  func " << myId << " future " << ll << std::endl;
}

// function takes a promise from one thread and creates a value to provide as a promise to another thread.
void func2(std::promise<int> &jj, std::promise<int>&pp) {
    int myId = std::atomic_fetch_add(&kount, 1);   // get my identifier
    std::future<int> intFuture(jj.get_future());
    auto ll = intFuture.get();     // wait for the promise attached to the future

    auto promiseValue = ll * 100;   // create the value to provide as promised to the next thread in the chain
    pp.set_value(promiseValue);
    std::cout << "  func2 " << myId << " promised " << promiseValue << " ll was " << ll << std::endl;
}

// thread function that starts up and waits for a series of notifications for work to do.
void func3(Sync_queue<int> &q, int iBegin, int iEnd, int *pInts) {
    int myId = std::atomic_fetch_add(&kount, 1);

    int ll;
    q.get(ll);    // wait on a notification and when we get it, processes it.
    while (ll > 0) {
        std::cout << "  func3 " << myId << " start loop base " << ll << " " << iBegin << " to " << iEnd << std::endl;
        for (int i = iBegin; i < iEnd; i++) {
            pInts[i] = ll + i;
        }
        q.get(ll);  // we finished this job so now wait for the next one.
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::chrono::milliseconds myDur(1000);

    // create our various promise and future objects which we are going to use to synchronise our threads
    // create our three threads which are going to do some simple things.
    std::cout << "MAIN #1 - create our threads." << std::endl;

    // thread T1 is going to wait on a promised int
    std::promise<int> intPromiseT1;
    std::thread t1(func, std::ref(intPromiseT1));

    // thread T2 is going to wait on a promised int and then provide a promised int to thread T3
    std::promise<int> intPromiseT2;
    std::promise<int> intPromiseT3;

    std::thread t2(func2, std::ref(intPromiseT2), std::ref(intPromiseT3));

    // thread T3 is going to wait on a promised int and then provide a promised int to thread Main
    std::promise<int> intPromiseMain;
    std::thread t3(func2, std::ref(intPromiseT3), std::ref(intPromiseMain));

    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2 - provide the value for promise #1" << std::endl;
    intPromiseT1.set_value(22);

    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2.2 - provide the value for promise #2" << std::endl;
    std::this_thread::sleep_for(myDur);
    intPromiseT2.set_value(1001);
    std::this_thread::sleep_for(myDur);
    std::cout << "MAIN #2.4 - set_value 1001 completed." << std::endl;

    std::future<int> intFutureMain(intPromiseMain.get_future());
    auto t3Promised = intFutureMain.get();
    std::cout << "MAIN #2.3 - intFutureMain.get() from T3. " << t3Promised << std::endl;

    t1.join();
    t2.join();
    t3.join();

    int iArray[100];

    Sync_queue<int> q1;    // notification queue for messages to thread t11
    Sync_queue<int> q2;    // notification queue for messages to thread t12

    std::thread t11(func3, std::ref(q1), 0, 5, iArray);     // start thread t11 with its queue and section of the array
    std::this_thread::sleep_for(myDur);
    std::thread t12(func3, std::ref(q2), 10, 15, iArray);   // start thread t12 with its queue and section of the array
    std::this_thread::sleep_for(myDur);

    // send a series of jobs to our threads by sending notification to each thread's queue.
    for (int i = 0; i < 5; i++) {
        std::cout << "MAIN #11 Loop to do array " << i << std::endl;
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
        q1.put(i + 100);
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
        q2.put(i + 1000);
        std::this_thread::sleep_for(myDur);  // sleep a moment for I/O to complete
    }

    // close down the job threads so that we can quit.
    q1.put(-1);    // indicate we are done with agreed upon out of range data value
    q2.put(-1);    // indicate we are done with agreed upon out of range data value

    t11.join();
    t12.join();
    return 0;
}

この簡単なアプリケーションは次のような出力を作成します。

MAIN #1 - create our threads.
MAIN #2 - provide the value for promise #1
  func 1 future 22
MAIN #2.2 - provide the value for promise #2
  func2 2 promised 100100 ll was 1001
  func2 3 promised 10010000 ll was 100100
MAIN #2.4 - set_value 1001 completed.
MAIN #2.3 - intFutureMain.get() from T3. 10010000
MAIN #11 Loop to do array 0
  func3 4 start loop base 100 0 to 5
  func3 5 start loop base 1000 10 to 15
MAIN #11 Loop to do array 1
  func3 4 start loop base 101 0 to 5
  func3 5 start loop base 1001 10 to 15
MAIN #11 Loop to do array 2
  func3 4 start loop base 102 0 to 5
  func3 5 start loop base 1002 10 to 15
MAIN #11 Loop to do array 3
  func3 4 start loop base 103 0 to 5
  func3 5 start loop base 1003 10 to 15
MAIN #11 Loop to do array 4
  func3 4 start loop base 104 0 to 5
  func3 5 start loop base 1004 10 to 15
6

約束はもう一方の端です。

futureによって計算されているasyncの値を取得する必要があるとします。しかし、それを同じスレッドで計算したくないし、「今」スレッドを生成することすらしません - たぶんあなたのソフトウェアはプールからスレッドを選ぶように設計されていたので、あなたは知りませんwhoは最後にche計算を実行します。

今、あなたはこの(まだ知られていない)スレッド/クラス/エンティティに何を渡しますか?これはの結果なので、futureを渡さないでください。 接続されたfutureに、ワイヤのもう一方の端を表すものを渡したいのであれば、知識なしでfutureを照会するだけです。誰が実際に何かを計算したり書いたりするかについて。

これがpromiseです。それはあなたのfutureに接続されたハンドルです。 futureスピーカーで、get()を使用すると、何らかの音が出るまで聴き始めます。promiseは、マイクです。マイクだけではなく、マイクをあなたが持っているスピーカーに単線で接続したものです。相手が誰かを知っているかもしれませんが、知る必要はありません - あなたはそれを渡し、相手が何かを言うまで待つだけです。

1
Narcolessico

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;
}
0
Zhang