I am frankly disappointed with C++0x futures. They lack a major feature–composability. It’s like having Booleans without OR or AND.
But let me first explain what futures are and how they’re implemented in C++0x.
What is a future?
In a nutshell, a future is an asynchronous function call. You call a function, but don’t wait until it completes–the body of the function executes in a separate thread. Instead you get a future, a promise to deliver the result at some later point in time. Only when your program desperately needs that value, do you synchronize on the future. If the calculation hasn’t been completed by then, you block until it has. Otherwise you get the result of the calculation.
C++0x futures
C++ splits the implementation of futures into a set of small blocks–it almost creates an assembly language of futures. Here are the primitives.
-promise
A promise is a vehicle for passing the return value (or an exception) from the thread executing a function to the thread that cashes in on the function future. The function (or a callable object) that is executed in a separate thread must have access to the promise, and it explicitly sets the return value.
You can think of a promise as a primitive channel; the function in this case is the sender. The equivalent of the “send” method is called promise::set_value (to pass an exception to the caller, the callee can call promise::set_exception.
There is no corresponding “receive” method–the receiving is abstracted into the future object.
Here’s some code that illustrates the use of promise. Notice how the function to be called asynchronously must be aware of the promise.
void asyncFun(promise<int> intPromise) { int result; try { // calculate the result intPromise.set_value(result); } catch (MyException e) { intPromise.set_exception(std::copy_exception(e)); } }
The calling thread creates the promise and passes it to the worker thread executing asyncFun (I’ll show the details later).
-future
A future is the synchronization object constructed around the receiving end of the promise channel. The calling thread obtains a future using promise::get_future.
The most important method of the future is get. If the result is not ready, get will block. When get completes, it either returns a value or throws an exception. The return value or the exception has been set by the called function through the promise associated with the future (see above).
get can also be split into its more basic parts: wait, optionally followed by has_value (or has_exception) and the call to get, which is guaranteed not to block. The advantage of the latter approach is that one can use versions of wait, wait_for and wait_until, that set timeouts.
There is also an asynchronous method, is_ready, that can be used for polling rather than blocking.
There are two separate implementations of future: regular future (which used to be called unique_future in Draft Standard) that works like a unique_ptr as far as passing values goes, and shared_future that works more like shared_ptr. For instance, the method get of future cannot be called twice because it transfers the ownership of the result.
Here’s an example how a future could be used in the caller’s code:
promise<int> intPromise; future<int> intFuture = intPromise.get_future(); std::thread t(asyncFun, std::move(intPromise)); // do some other stuff int result = intFuture.get(); // may throw MyException
-packaged_task
In most cases the use of promises can be hidden from the programmer. There is a template class, packaged_task that takes any function (or callable object) and instruments it for use with futures. In essence, it creates a promise and calls the function from inside a try/catch block. If the function returns, packaged_task puts the value in the promise, otherwise it sets the exception. A packaged_task is a callable object (has the function call operator() defined) can be passed directly to a thread.
Here’s a more complete example:
vector<int> primesUpTo(int n); int main() { packaged_task<vector<int>(int)> task(&primesUpTo); future<vector<int>> fut = task.get_future(); thread t(move(task), 100); t.detach(); // do some other work while the primes are computing vector<int> primes = fut.get(); // print primes return 0; }
Composability
So far so good. The creation of futures might be a bit verbose in C++, but it can be easily adapted to concrete environments. The futures standard library should be treated as a set of primitive building blocks from which to build higher abstractions.
Notice how C++ separated the channel (promise) from the synchronization mechanism (future); and thread creation from communication and synchronization. You may, for instance, use futures with thread pools or create threads on the spot.
What was omitted from the standard, however, was the ability to compose futures. Suppose you start several threads to perform calculations or retrieve data in parallel. You want to communicate with those threads using futures. And here’s the problem: you may block on any individual future but not on all of them. While you are blocked on one, other futures might become ready. Instead of spending your precious time servicing those other futures, you might be blocked on the most time-consuming one. The only option to process futures in order of completion is by polling (calling is_ready on consecutive futures in a loop) and burning the processor.
The need for composability of synchronization events has been widely recognized. Windows has WaitForMultipleObjects, Unix has select, and several higher order languages have explicit constructs that serve the same purpose. But that’s a topic for my next installment.
Acknowledgments
I’d like to thank Anthony Williams for helpful comments. Among other things, he is the author of one of the futures proposals for Boost. A stripped down version of it will become part of the threadpool library. Unfortunately, the stripping got rid of composability features.
March 3, 2009 at 8:41 pm
Composability is an extremely complicated mater without proper kernel support.
It’s an incredibly complicated task to implement such feature with pthreads, or even just on linux.
With pthreads you don’t have any option but use a global cond_var that is used to coordinate multiple waits.
Select and poll are useless as you can’t really wait on a cond var and using pipes for that simply don’t scale.
Linux allows one to create a file descriptor for a given futex and wait it with select/pool, but it’s racy and cannot be used to implement conv var semantics.
Of course one can re-implement the whole thing (locks, cond vars, wait queues, etc) on top of pthreads and have a mildly decent implementation of
the feature.
March 4, 2009 at 2:21 am
Rodrigo,
Why would using pipes not scale? Delivering signals through pipes as a way to unify signal handling and waiting for IO is an often used and well understood technique that has worked well in the past.
March 4, 2009 at 3:31 am
I agree. It is amazing to me that people keep forgetting what Tony Hoare did in CSP. I mean, he has probably all of three concepts, and one of them is “alt’ing”. It is a fundamental primitive. That provides the essence of non-determinism.
Ada has “accept” statements, with a much better syntax to boot.
March 4, 2009 at 3:53 am
There is a class of synchronization primitives called ‘eventcount’. eventcount is basically a “condition variable for lock-free algorithms”. They are extremely flexible and low-overhead blocking/signaling mechanism. You may see fairly portable implementation and several usage examples here (it uses only semaphore API, currently I port it to Linux and Win32):
http://software.intel.com/en-us/forums/intel-threading-building-blocks/topic/62364/
Eventcount can be easily bolted on top of promises (no need to re-implement the whole thing). Here is a sketch:
March 4, 2009 at 3:54 am
And here is a variant based on concurrent_queue (no polling):
March 4, 2009 at 2:29 pm
it’s a bit hard to understand
March 4, 2009 at 5:34 pm
[…] Broken promises–C++0x futures I am frankly disappointed with C++0x futures. They lack a major feature–composability. It’s like having […] […]
March 10, 2009 at 12:54 pm
[…] by Bartosz Milewski under C++, Concurrency, Haskell, ML, Multicore, Programming In my last post I criticized the C++0x implementation of futures for their lack of composability. I now feel […]
April 3, 2009 at 9:04 pm
[…] Eckel on C++ and Java: The C++ 0x standard is coming up with new features (one of them is named as ‘futures and promises’ ) I don’t know where/how these functions might be used and how many are still using this […]
April 13, 2009 at 10:43 am
Why does C++ need futures? I’ve implemented the equivalent functionality on many occasions with thread queues. (Queues also let you know as soon as any request is complete, so there is no composability problem.)
April 13, 2009 at 12:12 pm
Did you implement this functionality for a particular platform (Linux, Windows?), or in a portable way? The purpose of the new C++ Standard is to mandate certain functionality across platforms. Futures were picked because of their simplicity and relative safety.
March 12, 2010 at 5:11 pm
WaitForMultipleObjects has limitation – only 32 handles. On Windows, completion ports mechanism could be used for the purpose.
Thx for good article!
March 12, 2010 at 5:30 pm
I’m not sure how completion ports could be used to combine non-I/O objects. Do they accept event handles in place of file handles? I’m not that familiar with this aspect of Windows API.
March 19, 2010 at 9:44 am
Wow that’s very verbose. I’d like an API like this:
future ret = CallFunctionAsync(BlockingFunction, function, parameters);
// …
result = ret.get();
Can this be done?
August 2, 2010 at 12:03 pm
[…] message, then do some other work, and finally “force” the answer (that’s how futures work); although that’s still manageable. But if you send a message to one thread and set up a […]
January 2, 2011 at 12:25 pm
[…] Broken promises–C++0x futures March 200915 comments 5 […]
September 26, 2011 at 12:10 am
I just noticed that your primes example is incorrect. Firstly, “unique_future” has been renamed to just “future” since you wrote this, but this should be obvious.
The bigger issue is that packaged_task does not take the arguments in the constructor. Rather, you must pass the arguments to the function through the function call operator:
September 26, 2011 at 10:57 am
Thanks Anthony. I have updated the post. When I was writing it, I had no way to compile it (and futures were still unique_futures).
Minor correction: The declaration of task should read:
with this funny fake type
representing function returning vector of int and taking int as an argument.
October 3, 2011 at 11:15 am
[…] the context of Haskell, in Parallel Programming with Hints. And of course there is the problem of lack of composability of futures. So for the next 10 or so years we’ll have to stick to libraries, such as Microsoft PPL or […]
May 11, 2012 at 12:48 pm
[…] brings me to another topic: composable futures. I wrote a blog post some time ago, Broken Promises: C++0x Futures, in which I lamented the lack of composability of futures. I followed it with another blog, Futures […]
June 20, 2012 at 3:47 pm
[…] value until my worker thread sets the promise. I described this problem some time ago in my blog Broken Promises. Now it’s clear that composable futures should have been implemented using the continuation […]
August 3, 2012 at 3:09 pm
nice explanation
November 21, 2012 at 2:11 pm
[…] 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 […]
June 7, 2013 at 3:09 am
> I’m not sure how completion ports could be used to combine non-I/O objects. Do they accept event handles in place of file handles?
No. It must be a file handle with overlapped I/O enabled. But “file handle” is a stretchable concept: it includes pipes, mailslots, and sockets as well.
September 19, 2013 at 12:42 pm
[…] attempt at providing support for task-based parallelism with async tasks and non-composable futures (both seriously considered for deprecation in C++14). Thread-local variables were also standardized […]
February 26, 2014 at 11:59 am
[…] I was very disappointed with the design of C++11 std::future. I described my misgivings in: Broken Promises — C++0x futures. I also made a few suggestions as how to fix it: Futures Done Right. Five years went by and, lo and […]
April 7, 2014 at 4:54 am
[…] has been standardized in C++11. It is fair to say that the design of futures and promises is still quite limited, especially if compared with what is provided by C# and […]
January 2, 2015 at 12:15 am
[…] Bartosz Milewski provides a good writeup. […]
January 12, 2015 at 8:47 am
[…] Bartosz Milewski provides a good writeup. […]
May 7, 2015 at 2:21 am
[…] B. Milewski “Broken Promises c0x-futures,” https://bartoszmilewski.com/2009/03/03/broken-promises-c0x-futures/ […]
October 19, 2017 at 10:44 pm
[…] Broken promises–C++0x futures […]