Lines from Anthony William book:
std::launch::deferred
indicates that the function call is to be deferred until eitherwait()
orget()
is called on the future.X baz(X&); auto f7 = std::async(std::launch::deferred, baz, std::ref(x)); //run in wait() or get() //... f7.wait(); //invoke deferred function
What could be the benefits or differences of this code over a direct call (baz(ref(x))
)?
In other words, what's the point of having future here?
By default, std::async executed immediately its work package. The C++ runtime decides if the calculation happens in the same or a new thread. With the flag std::launch::async std::async will run its work package in a new thread.
If the deferred flag is set, a callable function will be stored together with its arguments, but the std::async function will not launch a new thread. Moreover, the callable function will be executed if either future::get() or future::wait() is called.
async( std::launch policy, Function&& f, Args&&... args ); (since C++20) The function template async runs the function f asynchronously (potentially in a separate thread which might be a part of a thread pool) and returns a std::future that will eventually hold the result of that function call.
std::future A future is an object that can retrieve a value from some provider object or function, properly synchronizing this access if in different threads. "Valid" futures are future objects associated to a shared state, and are constructed by calling one of the following functions: async. promise::get_future.
Suppose you have a thread pool.
The thread pool owns a certain number of threads. Say 10.
When you add tasks, they return a future, and they queue into the pool.
Threads in the pool wake up, grab a task, work on it.
What happens when you have 10 tasks in that pool waiting on a task later in the queue? Well, a deadlock.
Now, what if we return a deferred future from this pool.
When you wait on this deferred future it wakes up, checks if the task is done. If so, it finishes and returns.
Next, if the tasks is in the queue and not yet started, it steals the work from the queue and runs it right there, and returns.
Finally, if it is being run by the queue but not finished, it does something more complex. (the simplest version which usually works is that it blocks on the task, but that doesn't solve some pathological cases).
In any case, now if a task in the queue sleeps waits for another task in the queue to complete that isn't queue'd yet, we still get forward progress.
Another use of this is less arcane. Suppose we have some lazy values.
Instead of calculating them, we store shared futures with the calcuation steps in them. Now anyone who needs them just does a .get()
. If the value has already been calculated, we get the value; otherwise, we calculate it, then get it.
Later, we add in a system to do some work on idle or in another thread. These replace said deferred lazy futures in some cases, but not in others.
I think, the main benefit is that it might be executed in a different thread - the one which actually reads the future. This allows to transfer 'units of work' between threads - i.e. thread 1 creates the future, while thread 2 calls wait
on it.
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