Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are some threads deferred?

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?

like image 495
template boy Avatar asked Feb 11 '23 10:02

template boy


2 Answers

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.

like image 195
Jonathan Wakely Avatar answered Feb 15 '23 12:02

Jonathan Wakely


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)

like image 31
Christophe Avatar answered Feb 15 '23 11:02

Christophe