Skip to content

Commit f6c19d6

Browse files
committed
Expand CP.61 to talk about the general "factory" pattern.
1 parent 9864022 commit f6c19d6

File tree

1 file changed

+56
-17
lines changed

1 file changed

+56
-17
lines changed

CppCoreGuidelines.md

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14926,7 +14926,7 @@ This section looks at passing messages so that a programmer doesn't have to do e
1492614926
Message passing rules summary:
1492714927

1492814928
* [CP.60: Use a `future` to return a value from a concurrent task](#Rconc-future)
14929-
* [CP.61: Use an `async()` to spawn a concurrent task](#Rconc-async)
14929+
* [CP.61: Use `async()` to spawn concurrent tasks](#Rconc-async)
1493014930
* message queues
1493114931
* messaging libraries
1493214932

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

1495514955
???
1495614956

14957-
### <a name="Rconc-async"></a>CP.61: Use an `async()` to spawn a concurrent task
14957+
### <a name="Rconc-async"></a>CP.61: Use `async()` to spawn concurrent tasks
1495814958

1495914959
##### Reason
1496014960

14961-
A `future` preserves the usual function call return semantics for asynchronous tasks.
14962-
There is no explicit locking and both correct (value) return and error (exception) return are handled simply.
14961+
Similar to [R.12](#Rr-immediate-alloc), which tells you to avoid raw owning pointers, you should
14962+
also avoid raw threads and raw promises where possible. Use a factory function such as `std::async`,
14963+
which handles spawning or reusing a thread without exposing raw threads to your own code.
1496314964

1496414965
##### Example
1496514966

@@ -14974,25 +14975,63 @@ There is no explicit locking and both correct (value) return and error (exceptio
1497414975

1497514976
void async_example()
1497614977
{
14977-
try
14978-
{
14979-
auto v1 = std::async(std::launch::async, read_value, "v1.txt");
14980-
auto v2 = std::async(std::launch::async, read_value, "v2.txt");
14981-
std::cout << v1.get() + v2.get() << '\n';
14982-
}
14983-
catch (std::ios_base::failure & fail)
14984-
{
14978+
try {
14979+
std::future<int> f1 = std::async(read_value, "v1.txt");
14980+
std::future<int> f2 = std::async(read_value, "v2.txt");
14981+
std::cout << f1.get() + f2.get() << '\n';
14982+
} catch (const std::ios_base::failure& fail) {
1498514983
// handle exception here
1498614984
}
1498714985
}
1498814986

1498914987
##### Note
1499014988

14991-
Unfortunately, `async()` is not perfect.
14992-
For example, there is no guarantee that a thread pool is used to minimize thread construction.
14993-
In fact, most current `async()` implementations don't.
14994-
However, `async()` is simple and logically correct so until something better comes along
14995-
and unless you really need to optimize for many asynchronous tasks, stick with `async()`.
14989+
Unfortunately, `std::async` is not perfect. For example, it doesn't use a thread pool,
14990+
which means that it may fail due to resource exhaustion, rather than queueing up your tasks
14991+
to be executed later. However, even if you cannot use `std::async`, you should prefer to
14992+
write your own `future`-returning factory function, rather than using raw promises.
14993+
14994+
##### Example (bad)
14995+
14996+
This example shows two different ways to succeed at using `std::future`, but to fail
14997+
at avoiding raw `std::thread` management.
14998+
14999+
void async_example()
15000+
{
15001+
std::promise<int> p1;
15002+
std::future<int> f1 = p1.get_future();
15003+
std::thread t1([p1 = std::move(p1)]() mutable {
15004+
p1.set_value(read_value("v1.txt"));
15005+
});
15006+
t1.detach();
15007+
15008+
std::packaged_task<int()> pt2(read_value, "v2.txt");
15009+
std::future<int> f2 = pt2.get_future();
15010+
std::thread(std::move(pt2)).detach();
15011+
15012+
std::cout << f1.get() + f2.get() << '\n';
15013+
}
15014+
15015+
##### Example (good)
15016+
15017+
This example shows one way you could follow the general pattern set by
15018+
`std::async`, in a context where `std::async` itself was unacceptable for
15019+
use in production.
15020+
15021+
void async_example(WorkQueue *wq)
15022+
{
15023+
std::future<int> f1 = wq->enqueue([]() {
15024+
return read_value("v1.txt");
15025+
});
15026+
std::future<int> f2 = wq->enqueue([]() {
15027+
return read_value("v2.txt");
15028+
});
15029+
std::cout << f1.get() + f2.get() << '\n';
15030+
}
15031+
15032+
Any threads spawned to execute the code of `read_value` are hidden behind
15033+
the call to `WorkQueue::enqueue`. The user code deals only with `future`
15034+
objects, never with raw `thread`, `promise`, or `packaged_task` objects.
1499615035

1499715036
##### Enforcement
1499815037

0 commit comments

Comments
 (0)