@@ -14973,7 +14973,7 @@ This section looks at passing messages so that a programmer doesn't have to do e
14973
14973
Message passing rules summary:
14974
14974
14975
14975
* [CP.60: Use a `future` to return a value from a concurrent task](#Rconc-future)
14976
- * [CP.61: Use an `async()` to spawn a concurrent task ](#Rconc-async)
14976
+ * [CP.61: Use `async()` to spawn concurrent tasks ](#Rconc-async)
14977
14977
* message queues
14978
14978
* messaging libraries
14979
14979
@@ -15001,12 +15001,13 @@ There is no explicit locking and both correct (value) return and error (exceptio
15001
15001
15002
15002
???
15003
15003
15004
- ### <a name="Rconc-async"></a>CP.61: Use an `async()` to spawn a concurrent task
15004
+ ### <a name="Rconc-async"></a>CP.61: Use `async()` to spawn concurrent tasks
15005
15005
15006
15006
##### Reason
15007
15007
15008
- A `future` preserves the usual function call return semantics for asynchronous tasks.
15009
- There is no explicit locking and both correct (value) return and error (exception) return are handled simply.
15008
+ Similar to [R.12](#Rr-immediate-alloc), which tells you to avoid raw owning pointers, you should
15009
+ also avoid raw threads and raw promises where possible. Use a factory function such as `std::async`,
15010
+ which handles spawning or reusing a thread without exposing raw threads to your own code.
15010
15011
15011
15012
##### Example
15012
15013
@@ -15022,22 +15023,62 @@ There is no explicit locking and both correct (value) return and error (exceptio
15022
15023
void async_example()
15023
15024
{
15024
15025
try {
15025
- auto v1 = std::async(std::launch::async, read_value, "v1.txt");
15026
- auto v2 = std::async(std::launch::async, read_value, "v2.txt");
15027
- std::cout << v1.get() + v2.get() << '\n';
15028
- }
15029
- catch (std::ios_base::failure & fail) {
15026
+ std::future<int> f1 = std::async(read_value, "v1.txt");
15027
+ std::future<int> f2 = std::async(read_value, "v2.txt");
15028
+ std::cout << f1.get() + f2.get() << '\n';
15029
+ } catch (const std::ios_base::failure& fail) {
15030
15030
// handle exception here
15031
15031
}
15032
15032
}
15033
15033
15034
15034
##### Note
15035
15035
15036
- Unfortunately, `async()` is not perfect.
15037
- For example, there is no guarantee that a thread pool is used to minimize thread construction.
15038
- In fact, most current `async()` implementations don't.
15039
- However, `async()` is simple and logically correct so until something better comes along
15040
- and unless you really need to optimize for many asynchronous tasks, stick with `async()`.
15036
+ Unfortunately, `std::async` is not perfect. For example, it doesn't use a thread pool,
15037
+ which means that it may fail due to resource exhaustion, rather than queueing up your tasks
15038
+ to be executed later. However, even if you cannot use `std::async`, you should prefer to
15039
+ write your own `future`-returning factory function, rather than using raw promises.
15040
+
15041
+ ##### Example (bad)
15042
+
15043
+ This example shows two different ways to succeed at using `std::future`, but to fail
15044
+ at avoiding raw `std::thread` management.
15045
+
15046
+ void async_example()
15047
+ {
15048
+ std::promise<int> p1;
15049
+ std::future<int> f1 = p1.get_future();
15050
+ std::thread t1([p1 = std::move(p1)]() mutable {
15051
+ p1.set_value(read_value("v1.txt"));
15052
+ });
15053
+ t1.detach();
15054
+
15055
+ std::packaged_task<int()> pt2(read_value, "v2.txt");
15056
+ std::future<int> f2 = pt2.get_future();
15057
+ std::thread(std::move(pt2)).detach();
15058
+
15059
+ std::cout << f1.get() + f2.get() << '\n';
15060
+ }
15061
+
15062
+ ##### Example (good)
15063
+
15064
+ This example shows one way you could follow the general pattern set by
15065
+ `std::async`, in a context where `std::async` itself was unacceptable for
15066
+ use in production.
15067
+
15068
+ void async_example(WorkQueue& wq)
15069
+ {
15070
+ std::future<int> f1 = wq.enqueue([]() {
15071
+ return read_value("v1.txt");
15072
+ });
15073
+ std::future<int> f2 = wq.enqueue([]() {
15074
+ return read_value("v2.txt");
15075
+ });
15076
+ std::cout << f1.get() + f2.get() << '\n';
15077
+ }
15078
+
15079
+ Any threads spawned to execute the code of `read_value` are hidden behind
15080
+ the call to `WorkQueue::enqueue`. The user code deals only with `future`
15081
+ objects, never with raw `thread`, `promise`, or `packaged_task` objects.
15041
15082
15042
15083
##### Enforcement
15043
15084
0 commit comments