The flexibility that default policy for std::async permits both async and sync task execution leads to uncertainty when accessing thread_locals, implies that the task may never execute, and affects program logic for timeout-based wait calls.
When requesting a function f to be run in accord with a std::async, there are two standard launch policies, each represented by an enumerator in the std::launch scoped enum:
- The
std::launch::asynclaunch policy:fmust be run asynchronously on a different thread. - The
std::launch::deferredlaunch policy:fmay run only whengetorwaitis called on the future returned bystd::async.
In the 2nd policy, things go like this: f’s execution is deferred until a caller invokes get or wait, upon which time f will execute synchronously and block the caller until f finishes running. If neither get nor wait is called, f will never run.
The default launch policy, however, uses an OR-ed version of the above two policies:
|
|
This default policy permits f to be run either asynchronously or synchronously, depending on the decision of thread-management components of the Standard Library about the best step to do to avoid oversubscription and load balancing. This flexibility, however, also imtroduces some limitations:
-
It’s not possible to predict whether
fwill run concurrently witht, sincefmight be scheduled to run deferred. -
It’s not possible to predict whether
fruns on a thread different from the thread invokinggetorwaitonfut -
It may not be possible to predict whether
fruns at all, since it may not be possible to guarantee thatgetorwaitwill be called onfutalong every path through the program -
It affects
wait-based loops using timeouts, since callingwait_fororwait_untilon a task that’s deferred yields the valuestd::future_status_deferred, leading to following code run forever in some special cases:1 2 3 4 5 6 7 8 9 10 11 12 13using namespace std::literals; void f() { std::this_thread::sleep_for(1s); } auto fut = std::async(f); // run f in default launch policy while (fut.wait_for(100ms) != // f never yield read std::future_status::ready) // when deferred { ... }
Solution
To deal with these limitations, if we use both the default launch policy as well as the timeout-based wait calls, we need to check for deferred case:
|
|
In summary, using std::async with the default launch policy for a task is fine as long as following conditions are fulfilled:
- The task need not run concurrently with the thread calling
getorwait - It doesn’t matter which thread’s thread_local variables are read or written
- Either there’s a guarantee that
getorwaitwill be called on the future returned bystd::asyncor it’s acceptable that the task may never execute - Code using
wait_fororwait_untiltakes the possibility of deferred status into account like above example.
If any of the conditions above fails to hold, schedule the task for truly asynchronous execution:
|
|
A nice wrapper for this purpose goes like this:
|
|
|
|