0

I try to understand the concept of future and promise but have some issues to use them as return values from functions.

I came up with the code below

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

std::future<int> func(int i);

class A{
public:
    A(std::promise<int> && prms, int i):thread_([&]{
        std::cout << "Thread created in A\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        prms.set_value(i*2);
        thread_.detach();
    }){std::cout << "Call constructor of A\n";}
    ~A(){std::cout << "Call A's Destructor\n";}

private:
std::thread thread_;
};

int main()
{
    auto a_inst = func(2);
    a_inst.wait();
    std::cout << a_inst.get() << std::endl;

}

std::future<int> func(int i){
    std::promise<int> prms;
    //std::future<int> ftr = prms.get_future();
    A A1(std::move(prms), i);
    return prms.get_future();
}

So main should create a future from the return value of func, wait for the future to be assigned and then print it. But when I execute it, A is constructed and destructed without the thread being executed. Could someone guide me to the right direction?

edit

I added the vector containing the different promises. The thread doesn't directly process the promise but calls the processQueue function to do it. The class A object is created once and a unique_ptr reference is handed to the function where it's required.

#include <iostream>
#include <string>
#include <thread>
#include <future>
#include <chrono>
#include <vector>
#include <mutex>

class A{
public:
    int element_pointer = 0;
    A():thread_([&]() {
        std::cout << "Thread created in A\n";
        for(;;){
            std::this_thread::sleep_for(std::chrono::milliseconds(200));
            processQueue();
        }
        //local_promise.set_value(local_i*2);
    })
    {
        std::cout << "Call constructor of A\n";
    }

    ~A(){
        thread_.detach();
        std::cout << "Call A's Destructor\n";
    }

    void enq(std::promise<int> prms){
        std::lock_guard<std::mutex> guard(m);
        local_prmses_.push_back(std::move(prms));
        std::cout << "Queue now holds " << local_prmses_.size() << " elements\n";
    }

private:
    std::thread thread_;
    std::mutex m;
    std::vector<std::promise<int>> local_prmses_;
    void processQueue(){
        std::lock_guard<std::mutex> guard(m);
        std::cout << "execute processQueue()" << std::endl;
            if(element_pointer < local_prmses_.size()){
                for(element_pointer; element_pointer<local_prmses_.size(); element_pointer++){
                    local_prmses_[element_pointer].set_value(6);
                    std::cout << "Promise assigned\n";
                }

            } else {
                std::cout << "Nothing to process" << std::endl;
            }
        }
};

std::future<int> func(std::unique_ptr<A>& obj, int i);

int main()
{
    std::unique_ptr<A> obj = std::make_unique<A>();
    auto futr = func(obj, 9);
    //a_inst.wait();
    //std::cout << a_inst.get() << std::endl;
    for(;;){
        std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        if(futr.valid()){
            std::cout << "Yepeee!\n";
            std::cout << "Result is " << futr.get() << std::endl;
        }
        std::cout << "waiting...\n";
    }
}

std::future<int> func(std::unique_ptr<A>& obj, int i){
    std::promise<int> prms;
    auto fut = prms.get_future();
    obj->enq(std::move(prms));
    return fut;
}
po.pe
  • 1,047
  • 1
  • 12
  • 27

1 Answers1

3

2 problems:

  1. You are destructing your thread object in A before the thread can get detached. You could for example detach the thread in the A destructor, so control flow will reach it before A is completly destructed.

  2. The lambda in your thread object is working on the promise which is only alive in the func function scope. As soon as control flow leaves this scope, your promise get destructed and throws a broken promise exception. To prevent that, your lambda function should take ownership of the promise. If it takes ownership, you have to make sure to get the future before the ownership movement.

This leads to the following code (i just removed your errors. whether its a good design is another question ;-P):

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

std::future<int> func(int i);

class A{
public:
    A(std::promise<int> prms, int i):thread_([local_promise = std::move(prms), local_i = i]() mutable {
        std::cout << "Thread created in A\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        local_promise.set_value(local_i*2);
    })
    {
        std::cout << "Call constructor of A\n";
    }

    ~A(){
        thread_.detach();
        std::cout << "Call A's Destructor\n";
    }

private:
std::thread thread_;
};


int main()
{
    auto a_inst = func(9);
    a_inst.wait();
    std::cout << a_inst.get() << std::endl;
}

std::future<int> func(int i){
    std::promise<int> prms;
    auto fut = prms.get_future();
    A A1(std::move(prms), i);
    return fut;
}

live: http://coliru.stacked-crooked.com/a/8e2a3b982ad6e9fb

phön
  • 1,215
  • 8
  • 20
  • Okay thanks for that, it helped me alot. I went a step further in the direction I wanna go (with my vector of promises)... would you consider this a cleaner implementation now or am I still violating some design rules? – po.pe Aug 13 '18 at 15:29
  • @Humpawumpa Unfortunately there are still errors and design flaws. To start with the errors: You have a race condition on your vector holding the promises. That means: the thread in A is processing the vector elements while the main thread inserts something into it. You should guard it with a std::mutex. Then you need a way to shut down your thread cleanly. You dont have a stop condition. Designflaws: No need for a unique pointer. just create a on the stack and pass it by reference to your `func` function. Dont detach the thread. let the loop finish and join the thread cleanly. – phön Aug 13 '18 at 15:48
  • @Humpawumpa And you may want to remove processed promisses from the vector. consider switching to a std::queue if it fits your need (i guess it does). regarding your processing loop: i dont like the sleep. consider using a conditional_variable to signal that new work is rdy to process. by doing this you will stop waking your thread up even if no work is available and it allows you to wake the thread up when you want to exit your application (imagine you have 100 `A` objects and when doing a shutdown all wait for 200ms. thats bad – phön Aug 13 '18 at 15:49
  • You're right with the mutex, I don't plan to shutdown the thread, it shall process the vector/queue as long as the application is running. Why is a smart pointer is this case a wrong approach? Can I remove elements from my vector before I can be sure the future has been called? Or is the promise not required anymore once its value is set? – po.pe Aug 13 '18 at 15:54
  • @Humpawumpa then why not just shut it down with `A`? :-P Well you dont need a heap allocated object. its faster on the stack and easier to reason about. prefer the stack whenever possible. Yes, the promise and the future are pointing to the same shared state. The last one cleans up. If you have already stored the future, then the promise can leave the playground. Then the future will clean up after getting the value and beeing destructed. If you have no future attached, then yeah the result is lost. But there wasnt anyone interessted anyway mh? – phön Aug 13 '18 at 15:59
  • Sorry I'm from VHDL/C and just diving into C++ right now so I've some issues to get my head up to speed. Good point, join thread with destructor of A, makes sense and thanks for the clarification of the future and promise relation. So when promise is already assigned to the future, once the promise has a set value, it can be destroyed. – po.pe Aug 13 '18 at 16:06
  • @Humpawumpa No problem ;-) I overlooked something: since the thread in `A` uses the vector in `A`, you HAVE TO shutdown the thread. otherwise it tries to access the vector, but the vector is gone with `A` already. Regarding the future/promise: yes. The last instance alive cleans up. So the shared state (under the hood) is held alive as long as a promise or a future is refering to it. – phön Aug 13 '18 at 16:44