|
| 1 | +# Futures and Promises |
| 2 | + |
| 3 | +`std::future` and `std::promise` were introduced in C++11's concurrency API as |
| 4 | +the two ends of a read-write channel. `std::future` represents the |
| 5 | +consumer/read-end and `std::promise` the producer/write-end. |
| 6 | + |
| 7 | +A `std::future` object may be created either by a call to `std::async` or |
| 8 | +through a `std::packaged_task` or a `std::promise`. The latter two both provide |
| 9 | +a `get_future()` method for this purpose. In general, you use a `std::future` |
| 10 | +object to hold the return value of a (possibly) asynchronously run function |
| 11 | +call. |
| 12 | + |
| 13 | +## `std::future` and `std::promise` |
| 14 | + |
| 15 | +On the lowest level, a `std::future` is always associated with a |
| 16 | +`std::promise`. A future is used to wait for some value, while the promise is |
| 17 | +used to supply that precise value. Alongside `std::thread`, these two primitives |
| 18 | +could thus be used like so: |
| 19 | + |
| 20 | +```C++ |
| 21 | +int f(int x) { |
| 22 | + return x + 1; |
| 23 | +} |
| 24 | + |
| 25 | +// This will be the write-end |
| 26 | +std::promise<int> promise; |
| 27 | + |
| 28 | +// This will be the read-end |
| 29 | +auto future = promise.get_future(); |
| 30 | + |
| 31 | +// Launch f asynchronously |
| 32 | +std::thread thread([&promise] (int x) { |
| 33 | + promise.set_value(f(x)); |
| 34 | + }, |
| 35 | + 5 |
| 36 | +); |
| 37 | + |
| 38 | +// Get the value |
| 39 | +std::cout << future.get() << std::endl; |
| 40 | + |
| 41 | +// Make sure the main thread doesn't die |
| 42 | +// before the children threads (or detach |
| 43 | +// the child thread) |
| 44 | +thread.join(); |
| 45 | +``` |
| 46 | + |
| 47 | +As you can see, you use `std::promise::set_value` from the callee-thread to set |
| 48 | +the value to be communicated. `std::future` objects then have a method `get()` |
| 49 | +which returns this value. Actually, `get()` calls `std::future::wait()` until |
| 50 | +the return value is available, so you can also use `wait()` if you don't want |
| 51 | +to explicitly retrieve the value as soon as it is ready. |
| 52 | + |
| 53 | +Note that next to giving a promise a value, you can also give it an |
| 54 | +exception. For this, `std::promise` has a method `set_exception` which takes a |
| 55 | +`std::exception_ptr` object. There is no conversion from a `std::runtime_error` |
| 56 | +object to a `std::exception_ptr` or anything, so you cannot really just "create" |
| 57 | +an exception like that. You have to use `std::current_exception`, which holds a |
| 58 | +pointer to the currently caught exception during exception handling (in a catch |
| 59 | +block). |
| 60 | + |
| 61 | +When you then call `get()` on the corresponding future, the exception set will |
| 62 | +be rethrown (this is very useful when you don't have to do this manually, such |
| 63 | +as with `std::async`). Here an example: |
| 64 | + |
| 65 | +```C++ |
| 66 | +std::promise<int> promise; |
| 67 | + |
| 68 | +// Get the future |
| 69 | +auto future = promise.get_future(); |
| 70 | + |
| 71 | +std::thread thread([&promise] { |
| 72 | + try { |
| 73 | + throw std::runtime_error("Foobar!"); |
| 74 | + } catch(...) { |
| 75 | + promise.set_exception(std::current_exception()); |
| 76 | + } |
| 77 | +}); |
| 78 | + |
| 79 | +// Will throw right here! |
| 80 | +std::cout << future.get() << std::endl; |
| 81 | +``` |
| 82 | +
|
| 83 | +## `std::future` and `std::async` |
| 84 | +
|
| 85 | +Arguably the most common situation where you encounter `std::future`s is with |
| 86 | +calls to `std::async`. `std::async` is an alternative and higher-level way of |
| 87 | +executing a callable object in a separate thread, next to creating a |
| 88 | +`std::thread` object. When you call `std::async`, it returns a |
| 89 | +`std::future`. You could use it like so: |
| 90 | +
|
| 91 | +```C++ |
| 92 | +int f(int x) { |
| 93 | + return x + 1; |
| 94 | +} |
| 95 | +
|
| 96 | +// Note that a peculiarity about std::async is |
| 97 | +// that this call will not necessarily result in |
| 98 | +// f being run concurrently; it is based on the |
| 99 | +// scheduler's decision (to avoid oversubscription) |
| 100 | +auto result = std::async(f, 5); |
| 101 | +
|
| 102 | +std::cout << result.get() << std::endl; |
| 103 | +``` |
| 104 | + |
| 105 | +Clearly, this is a lot less work than manual promise/future |
| 106 | +management. Exceptions are also handled. |
| 107 | + |
| 108 | +For the purpose or our collective joy and happiness we could in fact implement a |
| 109 | +simplified version of `std::async` ourselves: |
| 110 | + |
| 111 | +```C++ |
| 112 | +int f(int x) { |
| 113 | + return x + 1; |
| 114 | +} |
| 115 | + |
| 116 | +template<typename Function, typename... Args> |
| 117 | +auto |
| 118 | +async(Function&& function, Args&&... args) { |
| 119 | + std::promise<std::result_of_t<Function(Args...)>> outer_promise; |
| 120 | + auto future = outer_promise.get_future(); |
| 121 | + |
| 122 | + auto lambda = [promise = std::move(outer_promise), function] |
| 123 | + (Args&&... args) |
| 124 | + mutable { |
| 125 | + try { |
| 126 | + promise.set_value(function(args...)); |
| 127 | + } catch (...) { |
| 128 | + promise.set_exception(std::current_exception()); |
| 129 | + } |
| 130 | + }; |
| 131 | + |
| 132 | + std::thread( |
| 133 | + std::move(lambda), |
| 134 | + std::forward<Args>(args)... |
| 135 | + ).detach(); |
| 136 | + |
| 137 | + return future; |
| 138 | +} |
| 139 | +``` |
| 140 | +
|
| 141 | +Or with packaged tasks: |
| 142 | +
|
| 143 | +```C++ |
| 144 | +template<typename Function, typename... Args> |
| 145 | +auto |
| 146 | +async_packaged(Function&& function, Args&&... args) { |
| 147 | + std::packaged_task<std::remove_reference_t<Function>> outer_task(function); |
| 148 | + auto future = outer_task.get_future(); |
| 149 | + auto lambda = [task = std::move(outer_task)] (Args&&... args) mutable { |
| 150 | + task(args...); |
| 151 | + }; |
| 152 | +
|
| 153 | + std::thread( |
| 154 | + std::move(lambda), |
| 155 | + std::forward<Args>(args)... |
| 156 | + ).detach(); |
| 157 | +
|
| 158 | + return future; |
| 159 | +} |
| 160 | +``` |
0 commit comments