17 January 2014

Tasks with std::future and std::async

tasks and arrows

Let us consider a simple task:
"Use a worker thread to compute a value".

In the source code it can look like that:

std::thread t([]() { auto res = perform_long_computation(); };
Now, you would like to obtain the result when the computation is completed. How to do it efficiently?

A shared variable can be used.

MyResult sharedRes;
std::thread t([]() { sharedRes = perform_long_computation(); };

But unfortunately, the problem is not solved yet. You need to know that the thread t is finished and sharedRes contains a computed value. Morover, since sharedRes contains a global state and you need some kind of a synchronization point when saving a new state. A lot of constructions can be used here: mutexes, condition variables, etc...
BTW: a global variable is not a best idea either.

Maybe there is a better and simpler way of doing that?

Please look at this code:
auto result = std::async([]() { return perform_long_computation(); });
MyResult finalResult = result.get();

In the above code, you have everything you need: the task is called asynchronously, finalResult contains the computed value. There is even no global state... all the magic synchronization and call is done by STD.

Isn't that awesome? But what actually happened there?

The future

In C++11 in the Standard Library you have now all sorts of concurrency features. As usually we have threads, mutexes, atomics, etc. Fortunately, the library went even further and added some more higher level structures. In our example we need to look at futures and async.
If you do not want to get into much details, all you need to know is that std::future<T> holds a shared state and std::async allows you to run the code asynchronously. In our case you can rewrite it to:
std::future<MyResult> result = std::async([]() { 
    return perform_long_computation(); 
});
MyResult finalResult = result.get();
Thus result is not a direct value computed in the thread, but it is some form of a guard that makes sure the value is ready when you call .get() method. All the magic (the synchronization) happens underneath.
This opens some interesting possibilities! You can, for instance, play with Task Based Parallelism. You might now build some form of a pipeline where data flows from one side to the other and in the middle computation can be distributed among several threads.

Below, there is a simple idea of the mentioned approach: you divide your computation into several separate parts, call them asynchronously and at the end collect the final result. It is up to the system/library to decide if each part is called on a dedicated thread (if available), or just run it on only one thread. This makes the solution more scalable.
Task Based Parallelism idea

Notes

  • .get() can be called only once! The second time you will get exception. If you want to fetch the result from several threads or several times in single thread you can use std::shared_future.
  • std::async can run code in the same thread as the caller. Launch Policy can be used to force truly asynchronous call - std::launch::async or std::launch::deferred
  • when there is an exception in the code>future (inside a lambda or a functor) this exception will be propagated and rethrown in the .get() method.

References

Interested in new blog posts and occasional updates? Please sign up for my free newsletter.

Copyright Bartlomiej Filipek, 2016, Blogger platform
Any opinions expressed herein are in no way representative of those of my employers.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.