Skip to content

Commit 89c7fa7

Browse files
committed
Adding note on futures and promises
1 parent 044f982 commit 89c7fa7

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed

future-and-promise.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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

Comments
 (0)