I have really enjoyed reading Anthony Williams’s C++ Concurrency in Action and highly recommended it if you want to get up to speed on the new threading support provided in the C++11 standard.
However, I did run into trouble on my first reading when, after the explanations of std::thread
and the use of join()
or detach()
, std::async
was introduced as a means to return a value back from a thread. Briefly, this is achieved by std::async
returning a std::future
object from which the return value from the thread can be retrieved (or an exception rethrown if one has been raised during the running of the asynchronous thread).
All that was OK but in quick succession std::packaged_task
and std::promise
are introduced. There are some examples, but this was certainly the point I was beginning to feel I was no longer really ‘getting it’. I continued reading with a note to revisit this section later.
Fast forward a while and two sources of information I happened upon led me to the “aha!” moment I was looking for with this subject.
The first was when I got the latest edition of the Josuttis’s classic The Standard C++ Library. This has been updated for all the new C++11 library features and also now includes a chapter on concurrency. This takes a different approach to explaining the creation of threads by instead starting with std::async
and presenting it as a high-level convenience function to create a thread and retrieve a value. Only then does it then goes onto explain std::thread
and std::promise
. So, the key point I had missed from C++ Concurrency In Action on the first reading is that the std::async
functionality is actually built using std::thread
, std::promise
and std::future
.
This was further reinforced when I found a code fragment on Bartosz Milewski’s blog showing the use of std::promise
. This inspired me to put together the following code for a basic std::async
implementation in order to understand what this could be doing ‘under the hood’ and to help me get a better understanding of the interaction of these ‘building blocks’.
Please Note! This is only intended to be a very simplistic version of async
as, for example, it only accepts a single parameter for the function to be run asynchronously and does not allow any launch policy to be specified. It is presented to highlight the relationship between std::promise
and std::future
, and the fact that std::async
hides the promise part of this interaction. In fact, this is not the only way to achieve this as it could also be implemented as a std::packaged_task
, but that’s another story.
template<typename Function, typename Arg> std::future<typename std::result_of<Function(Arg)>::type> SimpleAsync(Function func, Arg arg) { typedef std::result_of<Function(Arg)>::type return_val; std::promise<return_val> prom; std::future<return_val> fut = prom.get_future(); std::thread thr([](std::promise<return_val>& prom, Function func, Arg arg) { try { prom.set_value(func(arg)); } catch (const std::exception& e) { prom.set_exception(std::current_exception()); } }, std::move(prom), func, arg); thr.detach(); return std::move(fut); }
Although the std::promise
is hidden from users by being created in the SimpleAsync
function (in order to be able to retrieve the corresponding std::future
object for the return value), it is actually moved into the lambda in order to ensure its lifetime matches that of the asynchronous thread.