155

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 つのアプローチの違いは何ですか? さらに重要なことは、どのユースケースでどちらを使用する必要があるのでしょうか?

4

4 に答える 4

183

実際、あなたが与えた例は、次のようなかなり長い関数を使用した場合の違いを示しています

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

パッケージ化されたタスク

Apackaged_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::asyncwithlaunch::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::~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());
}   

詳細については、問題について説明しているHerb Sutterの記事async~futureを参照してください。また、この動作は C++14 以降で指定されていましたが、C++11 でも一般的に実装されていることに注意してください。std::futuresstd::async

その他の違い

を使用すると、他のスレッドに移動できる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を呼び出す前に a を呼び出す必要がありますf.get()。そうしないと、future の準備ができていないため、プログラムがフリーズします。

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場合、およびそれらを他のスレッドに移動したり後で呼び出したりするためにまとめたい場合に使用します。または、クリスチャンを引用するには:

結局のところ、 astd::packaged_taskは実装するための低レベルの機能にすぎません (これが、 のような他の低レベルのものと一緒に使用した場合std::asyncよりも多くのことができる理由です)。簡単に言うと、 aは a にリンクされ、 aをラップして呼び出します(おそらく別のスレッドで)。std::asyncstd::threadstd::packaged_taskstd::functionstd::futurestd::asyncstd::packaged_task

于 2013-08-09T09:44:18.413 に答える
3

TL;DR

std::packaged_task allows us to get the std::future "bounded" to some callable, and then control when and where this callable will be executed without the need of that future object.

std::async enables the first, but not the second. Namely, it allows us to get the future for some callable, but then, we have no control of its execution without that future object.

Practical example

Here is a practical example of a problem that can be solved with std::packaged_task but not with std::async.

Consider you want to implement a thread pool. It consists of a fixed number of worker threads and a shared queue. But shared queue of what? std::packaged_task is quite suitable here.

template <typename T>
class ThreadPool {
public:
  using task_type = std::packaged_task<T()>;

  std::future<T> enqueue(task_type task) {
      // could be passed by reference as well...
      // ...or implemented with perfect forwarding
    std::future<T> res = task.get_future();
    { std::lock_guard<std::mutex> lock(mutex_);
      tasks_.push(std::move(task));
    }
    cv_.notify_one();
    return res;
  }

  void worker() { 
    while (true) {  // supposed to be run forever for simplicity
      task_type task;
      { std::unique_lock<std::mutex> lock(mutex_);
        cv_.wait(lock, [this]{ return !this->tasks_.empty(); });
        task = std::move(tasks_.top());
        tasks_.pop();
      }
      task();
    }
  }
  ... // constructors, destructor,...
private:
  std::vector<std::thread> workers_;
  std::queue<task_type> tasks_;
  std::mutex mutex_;
  std::condition_variable cv_;
};

Such functionality cannot be implemented with std::async. We need to return an std::future from enqueue(). If we called std::async there (even with deferred policy) and return std::future, then we would have no option how to execute the callable in worker(). Note that you cannot create multiple futures for the same shared state (futures are non-copyable).

于 2021-11-12T09:03:22.377 に答える
0

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

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

于 2013-08-09T09:44:44.790 に答える