Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What Limitation of std::async is Stroustrup Referring To?

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 – with async() you don't even know how many threads will be used because that's up to async() 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 using async() 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 threads 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.

like image 202
5gon12eder Avatar asked Oct 15 '14 01:10

5gon12eder


1 Answers

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.

like image 77
Anthony Williams Avatar answered Nov 15 '22 12:11

Anthony Williams