4

In a tutorial I am following, the author wrote a program that showed that the destructors of std::futures don't always execute the task. In the following program, 10 threads created with std::async() are moved into the vector, and then we wait for their destructors to run.

#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int main()
{
    std::cout << "Main thread id: " << std::this_thread::get_id() << std::endl;

    std::vector<std::future<void>> futures;
    for (int i = 0; i < 10; ++i)
    {
        auto fut = std::async([i]
        {
            std::this_thread::sleep_for(std::chrono::seconds(2));
            std::cout << std::this_thread::get_id() << " ";
        });
        futures.push_back(std::move(fut));
    }
}

The result is machine-dependent, but what we found was that only 6 threads were launched when the destructors ran (we only got 6 ids printed after the main thread id output). This meant that the other four were deferred, and deferred threads don't run during std::future's destructors.

My question is why were some threads forced to execute while others were deferred. What is the point of deferring them if the life of the std::future is ending?

template boy
  • 10,230
  • 8
  • 61
  • 97
  • Oversubscribing (make too many threads of the amount of cores/physical threads) can decrease system performance because of all the task switching involved. – Borgleader Dec 04 '14 at 18:51
  • How many cores/SMT threads does your test machine have? – Stephan Dollberg Dec 04 '14 at 19:02
  • @bamboon The output was shown on the tutorial, not on my machine – template boy Dec 04 '14 at 19:05
  • Do you mean "what is the point of _running_ them if the life of the std::future is ending?" Because otherwise your question makes no sense to me. – Jonathan Wakely Dec 04 '14 at 19:20
  • @JonathanWakely No, the system deferred the threads, but I don't know why. If the future is ending, then I thought the threads should run before they die too... – template boy Dec 04 '14 at 19:24
  • No, why would the system waste time running functions to produce a result that the program doesn't want? If the program is destroying a future without calling `get()` or `wait()` then it obviously doesn't want the result. – Jonathan Wakely Dec 04 '14 at 20:49

2 Answers2

6

the author wrote a program that showed that the destructors of std::futures don't always execute the task.

The destructors never execute the task. If the task is already executing in another thread the destructor waits for it to finish, but it does not execute it.

what we found was that only 6 threads were launched when the destructors ran

This is incorrect, the threads are not launched when the destructors run, they are launched when you call std::async (or some time after that) and they are still running when the destructors start, so the destructors must wait for them.

What is the point of deferring them if the life of the std::future is ending?

Again, they are not deferred when the destructor runs, they are deferred when std::async is called, they are still deferred when the destructor runs, and so they just get thrown away without being run, and the destructor doesn't have to wait for anything.

I don't know if you're quoting the tutorial and the author of that is confused, or if you're confused, but your description of what happens is misleading.

Each time you call std::async without a launch policy argument the C++ runtime decides whether to create a new thread or whether to defer the function (so it can be run later). If the system is busy the runtime might decide to defer the function because launching another thread would make the system even slower.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • He said verbatim: "The other tasks were scheduled not to execute in separate threads but in serial - they were deferred. And for deferred tasks, the destructor does not execute the task". Is that an accurate statement? – template boy Dec 04 '14 at 20:04
  • Yes, that's accurate ... "the destructor does not execute the task" is what I said (and the opposite of how you phrased it in your question!) – Jonathan Wakely Dec 04 '14 at 20:48
2

Your async() calls use the default launch policy, which is launch::async|launch::deferred meaning that "The function chooses the policy automatically (at some point). This depends on the system and library implementation, which generally optimizes for the current availability of concurrency in the system."

thread::hardware_concurrency may give you hints about maximum hardware concurrency on your system. This can contribute to explain why some threads are necessarily deferred (especially if your loop is grater than the hardware concurrency) or not. However, beware that other running processes might use the hardware concurrency as well.

Please note as well that your asynchronous threads make use of cout which could delay some of them due to synchronization (more here)

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Good answer. When I run `thread::hardware_concurrency()` on the [Coliru editor](http://coliru.stacked-crooked.com/a/8375791eefa5cbf8), it returns `1`. But yet none of the 10 threads were deferred. Why is that? – template boy Dec 04 '14 at 19:13
  • As said, we are in implementation specific behaviour here. And hardware_cocnurrency MAY give a hint but unfortunately MUST not do so: "The interpretation of this value is system- and implementation- specific, and may not be exact, but just an approximation." – Christophe Dec 04 '14 at 19:19
  • 1
    @templateboy It's implementation dependent as Jonathan's answer states. [It seems](http://coliru.stacked-crooked.com/a/932c3d3260bb1891) clang+libc++ does not defer launching the threads at all. – Praetorian Dec 04 '14 at 19:19