Actually the example you just gave shows the differences if you use a rather long function, such as
//! sleeps for one second and returns 1
auto sleep = [](){
std::this_thread::sleep_for(std::chrono::seconds(1));
return 1;
};
A packaged_task
won't start on it's own, you have to invoke it:
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
On the other hand, std::async
with launch::async
will try to run the task in a different thread:
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";
But before you try to use async
for everything, keep in mind that the returned future has a special shared state, which demands that future::~future
blocks:
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;
*/
So if you want real asynchronous you need to keep the returned future
, or if you don't care for the result if the circumstances change:
{
auto pizza = std::async(get_pizza);
/* ... */
if(need_to_go)
return; // ~future will block
else
eat(pizza.get());
}
For more information on this, see Herb Sutter's article async
and ~future
, which describes the problem, and Scott Meyer's std::futures
from std::async
aren't special, which describes the insights. Also do note that this behavior was specified in C++14 and up, but also commonly implemented in C++11.
By using std::async
you cannot run your task on a specific thread anymore, where std::packaged_task
can be moved to other threads.
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";
Also, a packaged_task
needs to be invoked before you call f.get()
, otherwise you program will freeze as the future will never become ready:
std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);
Use std::async
if you want some things done and don't really care when they're done, and std::packaged_task
if you want to wrap up things in order to move them to other threads or call them later. Or, to quote Christian:
In the end a
std::packaged_task
is just a lower level feature for implementingstd::async
(which is why it can do more thanstd::async
if used together with other lower level stuff, likestd::thread
). Simply spoken astd::packaged_task
is astd::function
linked to astd::future
andstd::async
wraps and calls astd::packaged_task
(possibly in a different thread).
p> Packaged task holds a task [function or function object]
and future/promise pair. When the task executes a return statement, it causes set_value(..)
on the packaged_task
's promise.
a> Given Future, promise and package task we can create simple tasks without worrying too much about threads [thread is just something we give to run a task].
However we need to consider how many threads to use or whether a task is best run on the current thread or on another etc.Such descisions can be handled by a thread launcher called async()
, that decides whether to create a new a thread or recycle an old one or simply run the task on the current thread. It returns a future .
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With