Skip to content

Commit d56d679

Browse files
authored
Expand CP.61 to talk about the general "factory" pattern. (#1621)
1 parent e8e0d10 commit d56d679

File tree

1 file changed

+55
-14
lines changed

1 file changed

+55
-14
lines changed

CppCoreGuidelines.md

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14973,7 +14973,7 @@ This section looks at passing messages so that a programmer doesn't have to do e
1497314973
Message passing rules summary:
1497414974

1497514975
* [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)
1497714977
* message queues
1497814978
* messaging libraries
1497914979

@@ -15001,12 +15001,13 @@ There is no explicit locking and both correct (value) return and error (exceptio
1500115001

1500215002
???
1500315003

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
1500515005

1500615006
##### Reason
1500715007

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.
1501015011

1501115012
##### Example
1501215013

@@ -15022,22 +15023,62 @@ There is no explicit locking and both correct (value) return and error (exceptio
1502215023
void async_example()
1502315024
{
1502415025
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) {
1503015030
// handle exception here
1503115031
}
1503215032
}
1503315033

1503415034
##### Note
1503515035

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.
1504115082

1504215083
##### Enforcement
1504315084

0 commit comments

Comments
 (0)