packaged_taskとasyncの違いは何ですか


134

C ++ 11のスレッドモデルで作業しているときに、

std::packaged_task<int(int,int)> task([](int a, int b) { return a + b; });
auto f = task.get_future();
task(2,3);
std::cout << f.get() << '\n';

そして

auto f = std::async(std::launch::async, 
    [](int a, int b) { return a + b; }, 2, 3);
std::cout << f.get() << '\n';

まったく同じことをしているようです。で実行std::asyncした場合、大きな違いがある可能性があることは理解していますが、std::launch::deferredこの場合は違いますか?

これらの2つのアプローチの違いは何ですか?さらに重要なことに、どのユースケースでどちらを使用するべきですか?

回答:


161

実際、先ほどの例は、次のようなかなり長い関数を使用した場合の違いを示しています。

//! sleeps for one second and returns 1
auto sleep = [](){
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 1;
};

パッケージタスク

Aは、packaged_taskそれが自分だ上で起動しません、あなたはそれを起動する必要があります。

std::packaged_task<int()> task(sleep);

auto f = task.get_future();
task(); // invoke the function

// You have to wait until task returns. Since task calls sleep
// you will have to wait at least 1 second.
std::cout << "You can see this after 1 second\n";

// However, f.get() will be available, since task has already finished.
std::cout << f.get() << std::endl;

std::async

一方、std::asyncwith launch::asyncは別のスレッドでタスクを実行しようとします。

auto f = std::async(std::launch::async, sleep);
std::cout << "You can see this immediately!\n";

// However, the value of the future will be available after sleep has finished
// so f.get() can block up to 1 second.
std::cout << f.get() << "This will be shown after a second!\n";

欠点

ただしasync、すべてに使用する前に、返されたフューチャーには特別な共有状態があることに注意してfuture::~futureください。

std::async(do_work1); // ~future blocks
std::async(do_work2); // ~future blocks

/* output: (assuming that do_work* log their progress)
    do_work1() started;
    do_work1() stopped;
    do_work2() started;
    do_work2() stopped;
*/

したがって、本当の非同期が必要なfuture場合は、返されたを維持する必要があります。または、状況が変化しても結果を気にしない場合は、次のようになります。

{
    auto pizza = std::async(get_pizza);
    /* ... */
    if(need_to_go)
        return;          // ~future will block
    else
       eat(pizza.get());
}   

これに関する詳細については、ハーブサッターの記事を参照asyncして~future、問題を説明し、そしてスコット・マイヤーさんstd::futuresからはstd::async特別ではない洞察を説明しています、。また、この動作はご注意くださいC ++ 14とアップに指定されましたが、だけでなく、一般的にC ++ 11に実装されています。

さらなる違い

を使用std::asyncすると、特定のスレッドでタスクを実行できなくstd::packaged_taskなり、他のスレッドに移動できます。

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);

std::cout << f.get() << "\n";

また、をpackaged_task呼び出す前にを呼び出す必要がありますf.get()。そうしないと、将来の準備ができなくなるため、プログラムがフリーズします。

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);

TL; DR

使用std::asyncあなたには、いくつかの物事が行われ、それが完了したら、本当に気にしない、としたい場合はstd::packaged_task、あなたが他のスレッドに移動するか、後でそれらを呼び出すために物事をラップする場合。または、クリスチャンを引用するには:

結局のところ、a std::packaged_taskは実装するための下位レベルの機能にすぎstd::asyncません(そのためstd::async、などの他の下位レベルのものと一緒に使用した場合よりも多くの機能を実行できますstd::thread)。単に話されたa std::packaged_taskはaにstd::functionリンクされてstd::futureおりstd::asyncstd::packaged_task(おそらく別のスレッドで)a をラップして呼び出します。


9
(getを呼び出した場合と同様に)asyncによって返されたfutureが破棄時にブロックされるのに対し、packaged_taskから返されたものはそうではないことを追加する必要があります。
John5342 2013

22
結局のところ、a std::packaged_taskは実装するための下位レベルの機能にすぎstd::asyncません(そのためstd::async、などの他の下位レベルのものと一緒に使用した場合よりも多くの機能を実行できますstd::thread)。単に話されたa std::packaged_taskはaにstd::functionリンクされてstd::futureおりstd::asyncstd::packaged_task(おそらく別のスレッドで)a をラップして呼び出します。
Christian Rau 2013

〜future()ブロックでいくつかの実験を行っています。将来のオブジェクト破棄に対するブロッキング効果を再現できませんでした。すべてが非同期で機能しました。私はVS 2013を使用していますが、非同期を起動するときはstd :: launch :: asyncを使用しました。VC ++はこの問題を何らかの形で「修正」しましたか?
フランク・リュー、

1
@FrankLiu:ええと、N3451は受け入れられた提案で、(私の知る限り)C ++ 14に入りました。HerbがMicrosoftで動作していることを考えると、その機能がVS2013に実装されていても驚かないでしょう。C ++ 11の規則に厳密に従っているコンパイラでも、この動作は見られます。
Zeta

1
@Mikhailこの回答はC ++ 14とC ++ 17の両方に先行しているため、標準がなく、提案しかありませんでした。段落を削除します。
ゼータ、

1

パッケージタスクと非同期

p>パッケージタスクは、タスク[function or function object]とフューチャー/プロミスのペアを保持します。タスクは、return文を実行すると、それが原因set_value(..)packaged_taskの約束。

a> Future、promise、packageタスクが与えられれば、スレッドについてあまり心配することなく単純なタスクを作成できます[スレッドは、タスクを実行するために提供するものにすぎません]。

ただし、使用するスレッドの数や、タスクが現在のスレッドで実行されるか、別のスレッドで実行されるかなどを考慮する必要がありますasync()。このような決定は、と呼ばれるスレッドランチャーで処理でき、新しいスレッドを作成するか、古いスレッドをリサイクルするかを決定します。 1つ、または単に現在のスレッドでタスクを実行します。未来を返す。


0

「クラステンプレートstd :: packaged_taskは、呼び出し可能なターゲット(関数、ラムダ式、バインド式、または別の関数オブジェクト)をラップして、非同期で呼び出すことができるようにします。その戻り値またはスローされた例外は、アクセス可能な共有状態で保存されますstd :: futureオブジェクトを通じて」

「テンプレート関数asyncは、関数fを非同期的に(別のスレッドで実行する可能性があります)実行し、最終的にその関数呼び出しの結果を保持するstd :: futureを返します。」

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.