18

Near the beginning of this clip from C++ And Beyond, I heard something about problems with std::async. I have two questions:

  1. For a junior developer, is there a set of rules for what to do and what to avoid when using std::async?

  2. What are the problems presented in this video? Are they related to this article?

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277

2 Answers2

24

There are several issues:

  1. std::async without a launch policy lets the runtime library choose whether to start a new thread or run the task in the thread that called get() or wait() on the future. As Herb says, this is the case you most likely want to use. The problem is that this leaves it open to the QoI of the runtime library to get the number of threads right, and you don't know whether the task will have a thread to itself, so using thread-local variables can be problematic. This is what Scott is concerned about, as I understand it.

  2. Using a policy of std::launch::deferred doesn't actually run the task until you explicitly call get() or wait(). This is almost never what you want, so don't do that.

  3. Using a policy of std::launch::async starts a new thread. If you don't keep track of how many threads you've got, this can lead to too many threads running.

  4. Herb is concerned about the behaviour of the std::future destructor, which is supposed to wait for the task to complete, though MSVC2012 has a bug in that it doesn't wait.

For a junior developer, I would suggest:

  • Use std::async with the default launch policy.
  • Make sure you explicitly wait for all your futures.
  • Don't use thread-local storage in the async tasks.
Anthony Williams
  • 66,628
  • 14
  • 133
  • 155
  • Sounds like the language standard was going for one level abstraction too many, at least at this point. `std::threadpool` might have been a better thing to add, IMO. – GManNickG Sep 20 '12 at 20:57
  • 6
    `std::async` was added as a last-minute compromise. We didn't have time to specify thread pools properly, so we added `std::async` as a basic facility that covers the simple use cases. Thread pools will likely be in the next standard in some form. – Anthony Williams Sep 21 '12 at 08:28
  • Ah, I didn't know how late in the game it was, sounds pretty reasonable in that light. :) – GManNickG Sep 21 '12 at 18:25
  • 1
    About 4. Only future constructed with async (*only*) have to wait for the task to complete isn't it ? If you construct a future from a promise when that future gets destroyed it doesn't wait as I know – Ghita Jan 07 '13 at 19:13
  • @Ghita Yes, you're right: only `future`s constructed by `std::async` wait for the task. – Anthony Williams Jan 08 '13 at 10:20
  • FWIW MSVC fixed the BUG with async though afaik it was not really a bug bug, but a feature bug... Herb convinced implementers to implement it like that hoping he will be able convince ISO to do a breaking change... And ofc that did not happen – NoSenseEtAl Nov 21 '14 at 16:49
  • Yes, it was a deliberate choice to not-wait in MSVC2012. Herb failed to convince the committee, so it is good that they have fixed this with the latest MSVC. – Anthony Williams Nov 24 '14 at 16:14
  • Frankly, this implementation-dependent behaviour by default is a first class blunder. It's basically an undefined behaviour from the most straightforward variant of the interface. Not to mention that a class named "async" is not really expected to do a sequential job unless explicitely reminded to do what its name implies. – kuroi neko Dec 13 '14 at 18:29
  • 1
    @kuroineko I disagree. I think it's a really useful facility --- it allows you to let the implementation decide how much to oversubscribe the processor cores. A quality implementation will defer the decision, and then schedule the task asynchronously when a thread becomes available if it hasn't been run synchronously first. – Anthony Williams Dec 15 '14 at 08:53
  • @AnthonyWilliams That's a great vision that will come true when C++ will have taken over the world and operating systems will have been replaced by C++ runtimes. In the meantime, it's just an undefined behaviour that will let one version sit idle on one CPU while the other launches one thread per call, depending on the version of the compiler you use. – kuroi neko Dec 15 '14 at 15:03
  • 1
    -1 for default launch policy: that is only practical if you do not care if the task will ever be started before a `.get()` or `.wait()` is done. Which requires introspection of the task being done. In some situations you won't care: but in many, you will. In general, it is rare that you should be working with low level threading primitives and not care if the work you are being done is threaded or not... – Yakk - Adam Nevraumont May 29 '15 at 17:27
8

I could not disagree more about the advice to use the default policy.

If you go through the pain of designing independent computation units, you probably don't expect them to run sequentially while half a dozen CPUs twiddle their thumbs, which can "legally" happen depending on the compiler you choose.

The implicit assumption of the default behaviour is that some sophisticated thread pool mechanism will optimize task placement (possibly letting some run sequentially on the caller's CPU), but that is pure phantasy, since nothing specifies what the C++ runtime must do (which would go way beyond the scope of a compiler runtime anyway).

This looks more like undefined behaviour by design to me.

A class named "async" should launch asynchronous execution units, unless some explicit and deterministic behaviour parameter tells it otherwise.

Frankly, except for debugging purpose, I can't see a use for launch::deferred, unless you plan on writing your own pseudo-scheduler, in which case you'll be better off using plain threads.

So my advice would be to specify launch::async when you use async, (telling the compiler something like "hey, I want some async task, but really async, ok?") and not to use async at all if you just want to execute tasks sequentially.

If you run into trouble with your async tasks, it can be convenient to revert to deferred policy to debug them more easily, but that's about it.

kuroi neko
  • 8,479
  • 1
  • 19
  • 43