std::async(3)
π¦ std::async π¦
ok so real talk, this is the manual page for std::async(3), straight out of the C++ Standard Library β and bestie, it's a whole mood. It's giving "I run your function but like... elsewhere". Buckle up, part 1 goes hard. π
π NAME π
std::async β yeah that's it, the name is literally std::async. Iconic of it to serve nothing else in the NAME section. periodt. β¨
π Synopsis π
Declared up in the header <future>, and it's got variants because of course it does:
Defined in header <future>
template< class F, class... Args >
std::future<typename std::result_of<typename std::decay<F>::type(
typename std::decay<Args>::type...)>::type>
async( F&& f, Args&&... args ); (1) (since C++11) (until C++17)
template< class F, class... Args >
std::future<std::invoke_result_t<std::decay_t<F>,
std::decay_t<Args>...>>
async( F&& f, Args&&... args ); (1) (since C++17) (until C++20)
template< class F, class... Args >
[[nodiscard]]
std::future<std::invoke_result_t<std::decay_t<F>,
std::decay_t<Args>...>>
async( F&& f, Args&&... args ); (1) (since C++20)
template< class F, class... Args >
std::future<typename std::result_of<typename std::decay<F>::type(
typename std::decay<Args>::type...)>::type>
async( std::launch policy, F&& f, Args&&... args ); (2) (since C++11) (until C++17)
template< class F, class... Args >
std::future<std::invoke_result_t<std::decay_t<F>,
std::decay_t<Args>...>>
async( std::launch policy, F&& f, Args&&... args ); (2) (since C++17) (until C++20)
template< class F, class... Args >
[[nodiscard]]
std::future<std::invoke_result_t<std::decay_t<F>,
std::decay_t<Args>...>>
async( std::launch policy, F&& f, Args&&... args ); (2) (since C++20)
So what's the vibe? The function template std::async runs the function f asynchronously β potentially in a separate thread which might be a part of a thread pool, iykyk β and returns a std::future that will eventually hold the result of that function call. π«Ά
1) Behaves as if (2) is called with policy being std::launch::async | std::launch::deferred.
2) Calls a function f with arguments args according to a specific launch policy policy (see below, bestie, keep reading).
π¨ the "your program is ill-formed" section (until C++20) π¨
If any of the following conditions is satisfied, the program is ill-formed, no cap:
Fis not MoveConstructible.- Any type in
Argsis not MoveConstructible. INVOKE(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...)is not a valid expression.
π¨ same energy, but C++20 onwards (since C++20) π¨
If any of the following is false, the program is ill-formed:
std::is_constructible_v<std::decay_t<F>, F>(std::is_constructible_v<std::decay_t<Args>, Args> && ...)std::is_invocable_v<std::decay_t<F>, std::decay_t<Args>...>
And the sync tea β: the call to std::async synchronizes with the call to f, and the completion of f is sequenced before making the shared state ready. Chef's kiss sequencing. π
πΈ Parameters πΈ
fβ the callable object to call, the main character of this whole operation. π«argsβ the parameters you're passing along tof.policyβ a bitmask value, where individual bits control the allowed methods of execution. yes bitmask, yes vibe.
π§ Return value π§
A std::future referring to the shared state created by this call to std::async. That future is your lil ticket to whatever f eventually hands back, whether that's a value or a whole dramatic exception. ποΈ
π₯ Launch policies π₯
β‘ Async invocation
If the async flag is set (i.e. (policy & std::launch::async) != 0), then:
std::async calls INVOKE(decay-copy(std::forward<F>(f)),
decay-copy(std::forward<Args>(args))...) as if in a new thread (until C++23)
std::async calls std::invoke(auto(std::forward<F>(f)),
auto(std::forward<Args>(args))...) as if in a new thread (since C++23)
β¦of execution represented by a std::thread object. Bestie, it's literally out here spawning a whole thread for you. π
The calls of decay-copy are evaluated (until C++23). The values produced by auto are materialized (since C++23) in the current thread. If the function f returns a value or throws an exception, it is stored in the shared state accessible through the std::future that std::async returns to the caller. So nothing gets lost, it's all kept safe in the shared state. π«Ά
π΄ Deferred invocation
If the deferred flag is set (i.e. (policy & std::launch::deferred) != 0), then std::async stores:
decay-copy(std::forward<F>(f)) and decay-copy(std::forward<Args>(args))... (until C++23)
auto(std::forward<F>(f)) and auto(std::forward<Args>(args))... (since C++23)
β¦in the shared state. And then it's nap time β lazy evaluation is performed:
- The first call to a non-timed wait function on the
std::futurethatstd::asyncreturned to the caller will evaluateINVOKE(std::move(g), std::move(xyz))in the current thread (which does not have to be the thread that originally calledstd::async), where:gis the stored value of
decay-copy(std::forward<F>(f)) (until C++23)
auto(std::forward<F>(f)) (since C++23)
and
xyzis the stored copy of
decay-copy(std::forward<Args>(args))... (until C++23)
auto(std::forward<Args>(args))... (since C++23)
- The result or exception is placed in the shared state associated with the returned
std::futureand only then it is made ready. All further accesses to the samestd::futurewill return the result immediately. obsessed with the caching honestly. β¨
π Other policies
If neither std::launch::async nor std::launch::deferred, nor any implementation-defined policy flag is set in policy, the behavior is undefined. Like, genuinely undefined. don't do it bestie. π
π― Policy selection
If more than one flag is set, it is implementation-defined which policy is selected. For the default (both the std::launch::async and std::launch::deferred flags are set in policy), the standard recommends (but does not require) utilizing available concurrency, and deferring any additional tasks. so it's a suggestion, not a promise, iykyk. π
If the std::launch::async policy is chosen:
- a call to a waiting function on an asynchronous return object that shares the shared state created by this
std::asynccall blocks until the associated thread has completed, as if joined, or else time out; and - the associated thread completion synchronizes-with the successful return from the first function that is waiting on the shared state, or with the return of the last function that releases the shared state, whichever comes first.
π Exceptions π
It throws:
std::bad_alloc, if the memory for the internal data structures cannot be allocated, orstd::system_errorwith error conditionstd::errc::resource_unavailable_try_again, ifpolicy == std::launch::asyncand the implementation is unable to start a new thread.- If
policyisstd::launch::async | std::launch::deferredor has additional bits set, it will fall back to deferred invocation or the implementation-defined policies in this case. so like, it recovers. graceful queen. π
- If
π¦ Notes π¦
The implementation may extend the behavior of the first overload of std::async by enabling additional (implementation-defined) bits in the default launch policy. plot twist but make it legal. π
Examples of implementation-defined launch policies are the sync policy (execute immediately, within the std::async call) and the task policy (similar to std::async, but thread-locals are not cleared). yes king, undocumented but real. β¨
β οΈ ok this next one is the big one bestie, read it twice. If thestd::futureobtained fromstd::asyncis not moved from or bound to a reference, the destructor of thestd::futurewill block at the end of the full expression until the asynchronous operation completes β essentially making code such as the following synchronous:
std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes
Likeβ¦ the second one literally can't start until the first finishes. that is SO not the async you ordered. π
Note that the destructors of std::futures obtained by means other than a call to std::async never block. only std::async's future does that. rude but true.
π Example π
// Run this code
#include <algorithm>
#include <future>
#include <iostream>
#include <mutex>
#include <numeric>
#include <string>
#include <vector>
std::mutex m;
struct X
{
void foo(int i, const std::string& str)
{
std::lock_guard<std::mutex> lk(m);
std::cout << str << ' ' << i << '\n';
}
void bar(const std::string& str)
{
std::lock_guard<std::mutex> lk(m);
std::cout << str << '\n';
}
int operator()(int i)
{
std::lock_guard<std::mutex> lk(m);
std::cout << i << '\n';
return i + 10;
}
};
template<typename RandomIt>
int parallel_sum(RandomIt beg, RandomIt end)
{
auto len = end - beg;
if (len < 1000)
return std::accumulate(beg, end, 0);
RandomIt mid = beg + len / 2;
auto handle = std::async(std::launch::async,
parallel_sum<RandomIt>, mid, end);
int sum = parallel_sum(beg, mid);
return sum + handle.get();
}
int main()
{
std::vector<int> v(10000, 1);
std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
X x;
// Calls (&x)->foo(42, "Hello") with default policy:
// may print "Hello 42" concurrently or defer execution
auto a1 = std::async(&X::foo, &x, 42, "Hello");
// Calls x.bar("world!") with deferred policy
// prints "world!" when a2.get() or a2.wait() is called
auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
// Calls X()(43); with async policy
// prints "43" concurrently
auto a3 = std::async(std::launch::async, X(), 43);
a2.wait(); // prints "world!"
std::cout << a3.get() << '\n'; // prints "53"
} // if a1 is not done at this point, destructor of a1 prints "Hello 42" here
ok we're not done yet β this page keeps going, part 2 has the rest bestie, don't close the tab ππ
Possible output:
The sum is 10000
43
world!
53
Hello 42
π©Ή Defect reports π©Ή
Okay so bestie, plot twist: the standard has been edited retroactively π β these are the defect reports that got applied backwards to previously published C++ standards. Yes, time travel is real, it's giving main character energy β¨
LWG 2021, applied to C++11 β as published, the return type was incorrect and the value category of arguments was unclear in the deferred case. It got corrected: the return type is fixed and it's now clarified that rvalues are used. πLWG 2078, applied to C++11 β the tea was unclear on whetherstd::system_errormay be thrown if the policy specifies launch policies other thanstd::launch::async. Now it can only be thrown ifpolicy == std::launch::async. no cap π«ΆLWG 2100, applied to C++11 β timed waiting functions could not timeout when thestd::launch::asyncpolicy is used. Now that's allowed. iconic behavior honestly β°LWG 2120, applied to C++11 β the behavior was unclear if no standard or implementation-defined policy is set. Spoiler alert: the behavior is undefined in this case πLWG 2752, applied to C++11 βstd::asyncmight not throwstd::bad_allocif the memory for the internal data structures cannot be allocated. Now it throws. rude but fair πLWG 3476, applied to C++20 β (the decayed types of)Fand the argument types were directly required to be move constructible. These requirements got removed^[1] π
^[1] The move-constructibility is already indirectly required by std::is_constructible_v. iykyk π§
π See also π
futureβ waits for a value that is set asynchronously (C++11) (class template) πΈ
okay that's literally the whole page bestie, you ate that π
β¨ go forth and std::async responsibly (and remember: undefined behavior is not a vibe). π¦
This lil glow-up was brought to you by cppreference.com, the 2024.06.10 edition, for the page std::async(3) β mwah, ily, bye! π«Άππ
π¦ where this page comes from
- nixpkgs unstable: stdman 2024.07.05
- opensuse-leap 16.0: stdman 2024.07.05-bp160.1.9 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms