私は、C++ 11のstd::thread
、std::async
、およびstd::future
の各コンポーネント(たとえば、 この回答 を参照)に精通しています。
しかし、私はstd::promise
が何であるか、それが何をしているのか、そしてどの状況でそれが最もよく使われているのかをよく理解することはできません。標準文書自体には、そのクラスの概要を超えた多くの情報は含まれていません。また、 just :: thread も含まれていません。
誰かがstd::promise
が必要とされ、それが最も慣用的な解決策である状況の簡単で簡潔な例をお願いします。
[futures.state]の言葉では、std::future
は非同期戻りオブジェクト( "共有状態から結果を読み取るオブジェクト")であり、std::promise
は非同期プロバイダーです。 ( "共有状態に結果を提供するオブジェクト")すなわち約束はあなたがset結果を出すことであるので、あなたはget関連からそれを得ることができます未来。
非同期プロバイダは、将来が参照する共有状態を最初に作成するものです。 std::promise
は非同期プロバイダの一種で、std::packaged_task
は別のものです。そしてstd::async
の内部の詳細は別のものです。それぞれが共有状態を作成し、その状態を共有するstd::future
を提供して、状態を準備することができます。
std::async
は、非同期の結果オブジェクトを提供し、内部的に非同期プロバイダを作成してタスクの完了時に共有状態を準備できるようにする、より高レベルの便利なユーティリティです。 std::packaged_task
(またはstd::bind
とstd::promise
)とstd::thread
でそれをエミュレートできますが、std::async
を使用する方がより安全で簡単です。
std::promise
は、非同期の結果を将来に渡したい場合のために、もう少し低レベルですが、結果を準備するコードを、std::async
に渡すのに適した単一の関数にまとめることはできません。たとえば、いくつかのpromise
sとそれに関連するfuture
sの配列があり、いくつかの計算を実行して各約束に対して結果を設定する単一のスレッドがあるとします。 async
は単一の結果を返すことのみを許可し、複数を返すにはasync
を数回呼び出す必要があり、これはリソースを浪費する可能性があります。
私は状況をもう少しよく理解しました(ここでの回答のために少なからず!)ので、私は自分自身の少しの記事を追加すると思いました。
C++ 11には2つの異なる概念がありますが、関連していますが、非同期計算(どこかで呼び出される関数)、および同時実行(thread、動作するもの)同時に)。 2つはやや直交する概念です。非同期計算は、関数呼び出しの異なるフレーバーにすぎませんが、スレッドは実行コンテキストです。スレッドはそれ自体で有用ですが、この説明の目的のために、スレッドを実装の詳細として扱います。
非同期計算には抽象化の階層があります。たとえば、いくつかの引数を取る関数があるとします。
int foo(double, char, bool);
最初に、テンプレート std::future<T>
があります。これはT
型の将来の値を表します。値は、結果を待つことでプログラムを効果的に同期するメンバー関数get()
を介して取得できます。あるいは、将来はwait_for()
をサポートします。これは、結果がすでに利用可能かどうかを調べるために使用できます。先物は、通常の戻り値型の非同期ドロップイン置換と見なされる必要があります。サンプル関数では、std::future<int>
が必要です。
次に、最上位から最下位までの階層に進みます。
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
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
これで最低レベルになりました:パッケージ化されたタスクを実装する方法は?これが 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_exception
のeitherが複数ある場合、同じ例外がスローされます。
ケース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();
}
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
おおまかに言って、std::promise
をstd::future
のもう一方の端と見なすことができます(これはfalseですが、説明のためにそうであるかのように考えることができます)。通信チャネルのコンシューマ側は、共有状態からデータを消費するためにstd::future
を使用しますが、プロデューサスレッドは共有状態に書き込むためにstd::promise
を使用します。
std::promise
は、非同期関数から返される情報のチャネルまたは経路です。 std::future
は、std::promise
に含まれる戻り値の準備ができるまで(呼び出し側に値が関数内で設定されることを意味します)、呼び出し側を待機させる同期メカニズムです。
非同期処理には、本当に3つのコアエンティティがあります。 C++ 11は現在それらの2つに焦点を合わせています。
いくつかのロジックを非同期的に実行するために必要なコアとなるものは以下のとおりです。
C++ 11では、(1)std::promise
で、および(3)std::future
で私が話していることを呼び出します。 std::thread
は(2)に対して公的に提供されている唯一のものです。実際のプログラムはスレッドとメモリのリソースを管理する必要があり、小さなタスクごとにスレッドを作成して破棄するのではなく、ほとんどのタスクをスレッドプールで実行する必要があるため、残念です。さらに悪いことに飢餓)。
Herb Sutterと他のC++ 11の脳の信頼関係者によると、Javaのようにstd::executor
を追加することを暫定的に計画しています。たぶん私たちはC++ 2014でそれを見るでしょう、しかし私の賭けはC++ 17にもっと似ています(そして彼らがこれらの標準をくぐらせたなら神は私たちを助けます)。
std::promise
は、promise/futureペアのエンドポイントとして作成され、std::future
(get_future()
メソッドを使用してstd :: promiseから作成される)はもう1つのエンドポイントです。これは、1つのスレッドがメッセージを介して別のスレッドにデータを提供するため、2つのスレッドが同期するための方法を提供する簡単なワンショット方式です。
一方のスレッドがデータを提供するという約束を作成し、もう一方のスレッドが将来その約束を収集すると考えることができます。このメカニズムは一度だけ使用できます。
約束/将来のメカニズムは、std::promise
のset_value()
メソッドを使用するスレッドからstd::future
のget()
を使用するスレッドへの一方向のみです。将来のget()
メソッドが複数回呼び出されると、例外が生成されます。
std::promise
を持つスレッドがその約束を満たすためにset_value()
を使わなかった場合、2番目のスレッドがstd::future
のget()
を呼び出して約束を集めると、2番目のスレッドはstd::promise
を持つ最初のスレッドによって約束が満たされるまで待ち状態に入ります。データを送信するためにset_value()
メソッドを使用するとき。
提案されているコルーチン 技術仕様書N4663プログラミング言語 - コルーチン用C++拡張 およびVisual Studio 2017 C++コンパイラによるco_await
のサポートにより、std::future
およびstd::async
を使用してコルーチン機能を作成することもできます。 https://stackoverflow.com/a/50753040/146697 にあるstd::future
とco_await
の使用を説明する1つのセクションとしての説明と例を参照してください。
次のコード例、単純なVisual Studio 2013 Windowsコンソールアプリケーションは、いくつかのC++ 11同時実行クラス/テンプレートおよびその他の機能の使用方法を示しています。これは、うまく機能するpromise/futureの使用、何らかのタスクを実行して停止する自律スレッド、およびより多くの同期動作が必要であり、複数の通知が必要なためpromise/futureのペアが機能しない用途を示します。
この例に関する1つの注意点は、さまざまな場所で追加された遅延です。これらの遅延は、std::cout
を使用してコンソールに表示されるさまざまなメッセージが明確で、複数のスレッドからのテキストが混ざらないようにするためにのみ追加されました。
main()
の最初の部分は、3つの追加スレッドを作成し、std::promise
とstd::future
を使用してスレッド間でデータを送信することです。おもしろい点は、メインスレッドがメインスレッドからのデータを待って何かをしてから3番目のスレッドT3にデータを送るスレッドT2を起動するところです。メインスレッド.
main()
の2番目の部分では、2つのスレッドと1組のキューを作成して、メインスレッドから2つの各スレッドへの複数のメッセージを許可します。 std::promise
とstd::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
約束はもう一方の端です。
future
によって計算されているasync
の値を取得する必要があるとします。しかし、それを同じスレッドで計算したくないし、「今」スレッドを生成することすらしません - たぶんあなたのソフトウェアはプールからスレッドを選ぶように設計されていたので、あなたは知りませんwhoは最後にche計算を実行します。
今、あなたはこの(まだ知られていない)スレッド/クラス/エンティティに何を渡しますか?これはの結果なので、future
を渡さないでください。 接続されたfuture
に、ワイヤのもう一方の端を表すものを渡したいのであれば、知識なしでfuture
を照会するだけです。誰が実際に何かを計算したり書いたりするかについて。
これがpromise
です。それはあなたのfuture
に接続されたハンドルです。 future
がスピーカーで、get()
を使用すると、何らかの音が出るまで聴き始めます。promise
は、マイクです。マイクだけではなく、マイクをあなたが持っているスピーカーに単線で接続したものです。相手が誰かを知っているかもしれませんが、知る必要はありません - あなたはそれを渡し、相手が何かを言うまで待つだけです。
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;
}