0

I was reading abut std::async with std::launch::async and read that with that policy, the callable will be called in a new thread.

So for sake of test, I did as follow:

struct Wrapper
    {   
        void consume()
        {
             std::cout << "consume " << std::this_thread::get_id() << std::endl;
            std::for_each(arr.begin(), arr.end(), [](int val) {std::cout << val; });
        }

        bool produce()
        {
            std::cout << "produce " << std::this_thread::get_id() << std::endl;

            arr = { 1,0,3 };
            return true;
        }
    };


    int main()
    {

        std::cout << "main " << std::this_thread::get_id() << std::endl;

        Wrapper wrap;
        std::future<bool> fut = std::async(std::launch::async, &Wrapper::produce, &wrap);

        if (fut.get())
            std::async(std::launch::async, &Wrapper::consume, &wrap);

    }

So based on that, I will excpect 3 threads :

  • thread 1 : the main thread
  • thread 2 : the first call of std::async (executing the produce fct)
  • thread 3 : the second call of std::async (executing the consume fct)

when I run the program i got :

enter image description here

Why is the two calls of the std::async have the same thread ID ??

Thank you.

Blood-HaZaRd
  • 2,049
  • 2
  • 20
  • 43
  • 1
    The implementation is allowed to use a thread pool. Creating new threads is very expensive, a thread pool spreads this cost over all the async tasks. – Richard Critten Sep 14 '18 at 12:33
  • So behind the scene there were 3 distincts thread, but as you mentionned, we used a thread pool resulting on the same ID. – Blood-HaZaRd Sep 14 '18 at 12:35
  • 5
    `produce` and `consume` are not executed concurrently. `consume` is called when `produce` finished. So these functions can be executed by the same thread. Call your code concurrently and you see two different IDs. – rafix07 Sep 14 '18 at 12:35
  • 2
    Also, I think thread ID's can be recycled so the fact that you have identical thread ID's does not actually mean you have the same thread. (Many implementations do avoid recycling thread ID's, but that is a Quality of Implementation thing.) – MSalters Sep 14 '18 at 12:40
  • @MSalters: yes right, in fatc as Richard said, I guess it is all about thread pools (because every tilme I excuted the two std::async calls have the same ID), and in addition to the no cioncurrency as rafix07 said, i got the same ID – Blood-HaZaRd Sep 14 '18 at 12:43
  • @rafix07 : how could I change it to be concurrent, as the future will wait for the promise ? – Blood-HaZaRd Sep 14 '18 at 12:46
  • 2
    @Blood-HaZaRd: You misunderstand. You say "same thread **because** same ID". That is wrong. I pointed out that you could have "same ID **despite** different threads". Therefore, your conclusion is wrong. (does not follow from the premise) – MSalters Sep 14 '18 at 12:48
  • @MSalters: I may missexplained my self. I am aware that there were 3 distincts threads. – Blood-HaZaRd Sep 14 '18 at 12:50
  • @Blood-HaZaRd: That is flipping from one wrong conclusion to another. You have 2 **or** 3 threads. You know that you have **at least** 2 threads because you have that many unique thread ID's. – MSalters Sep 14 '18 at 12:56
  • @MSalters : so let try to be clear :) if a thread pool is used we end up with 2 threads and in case of not we will end with 3 threads. Right ? – Blood-HaZaRd Sep 14 '18 at 13:00
  • @Blood-HaZaRd - it depends a thread pool can also shrink and grow the number of available threads (in the pool waiting to be used) based on external factors such as number of concurrent tasks, available system resources, etc – Richard Critten Sep 14 '18 at 15:28

2 Answers2

2

The standard says about std::async :

If launch​::​async is set in policy, calls INVOKE(DECAY_­COPY(std​::​forward<F>(f)), DECAY_­COPY(std​::​forward<Args>(args))...) as if in a new thread of execution [...]

Link

The important part is "as if in a new thread of execution". The implementation must behave as if the callable object was called from a new thread, but it's not required to actually run on a new thread. This leeway is provided so that the standard library implementer can reuse the same threads, perhaps by keeping a handy pool of threads on standby (a threadpool pattern), which can be more responsive than creating and destroying threats for every call to std::async. Though in theory an implementation could also choose to create and destroy threads each time, or do anything else that respects the requirements of the standard.

François Andrieux
  • 28,148
  • 6
  • 56
  • 87
0

Try this, there are three threads.

    #include<future>
    #include<iostream>
    #include<array>
    #include<algorithm>
    #include<thread>
    #include<vector>

    std::array<int, 100000> arr;
    int sum=0;

    struct Wrapper
    {   
        void consume()
        {
            std::cout << "consumer:" << std::this_thread::get_id() << std::endl;
            std::for_each(arr.begin(), arr.end(), [](int val) {sum+=val; });
        }

        void produce()
        {
            std::cout << "producer:" <<std::this_thread::get_id() << std::endl;
            
            int a=0;
            while(true)
            {
                if(a++>1e9)
                {
                    break;
                }
            }
        }
    };


    int main()
    {
        std::fill(arr.begin(), arr.end(), 1);

        std::cout << "main:" <<std::this_thread::get_id() << std::endl;

        Wrapper wrap;

        std::vector<std::future<void>> vec;
        vec.push_back(std::async(std::launch::async, &Wrapper::produce, &wrap));
        vec.push_back(std::async(std::launch::async, &Wrapper::consume, &wrap));

        #ifdef WAIT  //Is there any potencial problem if the block below does not run?
        for(auto& future:vec)
        {
            future.get();
        }
        #endif
    }
John
  • 2,963
  • 11
  • 33