In the “tutorial” chapter 5.3.5.3 of his book The C++ Programming Language (4th edition), Bjarne Stroustrup writes about the std::async
function.
There is an obvious limitation: Don't even think of using
async()
for tasks that share resources needing locking – withasync()
you don't even know how manythread
s will be used because that's up toasync()
to decide based on what it knows about the system resources available at the time of a call.
A similar exhortation can be found in the C++11-FAQ on his website.
“Simple” is the most important aspect of the
async()
/future
design; futures can also be used with threads in general, but don't even think of usingasync()
to launch tasks that do I/O, manipulates mutexes, or in other ways interact with other tasks.
Interestingly, he doesn't elaborate on this limitation when he returns to C++11's concurrency features in more detail in § 42.4.6 of his book. Even more interestingly, in this chapter he actually continues (compare to the statement on his website):
A simple and realistic use of
async()
would be to spawn a task to collect input from a user.
The documentation of async
on cppreference.com
does not mention any such limitations at all.
After reading some proposals and discussions that lead to the C++11 standard in its final form (which unfortunately I don't have access to), I understand that async
was incorporated very late into the C++11 standard and there was a lot of discussion how powerful this feature should be. In particular, I found the article Async Tasks in C++11: Not Quite There Yet by Bartosz Milewski a very good summary of the problems that have to be considered when implementing async
.
However, all discussed problems are related to thread_local
variables that are not destructed if a thread is recycled, spurious deadlocks or data access violations if a thread switches its task mid-action and both tasks hold a mutex
or recursive_mutex
respectively and so forth. These are serious concerns for implementors of the feature but if I understand correctly, the current specification of async
requires all these details be hidden from the user by executing the task either on the caller's thread or as if a new thread was created for the task.
So my question is: What am I not allowed to do with async
that I am allowed to do using thread
s manually and what is the reason for this limitation?
For example, is there anything wrong with the following program?
#include <future>
#include <iostream>
#include <mutex>
#include <vector>
static int tally {};
static std::mutex tally_mutex {};
static void
do_work(const int amount)
{
for (int i = 0; i < amount; ++i)
{
// Might do something actually useful...
const std::unique_lock<std::mutex> lock {tally_mutex};
tally += 1;
}
}
int
main()
{
constexpr int concurrency {10};
constexpr int amount {1000000};
std::vector<std::future<void>> futures {};
for (int t = 0; t < concurrency; ++t)
futures.push_back(std::async(do_work, amount / concurrency));
for (auto& future : futures)
future.get();
std::cout << tally << std::endl;
}
Obviously, if the runtime decides to schedule all tasks on the main thread, we will needlessly acquire the mutex over and over again for no good reason. But while this might be inefficient, it is not incorrect.
The "problem" with std::async is that by default you don't know whether or not it starts a thread. If your function needs to run on a separate thread then this is a problem as your function might not run until get() or wait() is called. You can pass std::launch::async to ensure the function is launched on its own thread, which is like a std::thread that cannot BG e detached.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With