C++ Promise and Future

C++11 introduced the concept of promise and future to a provide synchronization point between threads by sharing objects. The promise object owner (the producer thread) promises to produce a value – may not be immediately but sometime in future. The future owner (the consumer thread) expects a value once the associated promise is met by the producer.

The consumer waits on the future object until the value is set to the associated promise object by the producer.

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

int main() {
    
    auto prom = std::promise<std::string>();

    auto producer = std::thread([&prom](){
        std::cout << "Producer: waiting for a second..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));

        std::cout << "Producer: setting the value..." << std::endl;
        prom.set_value("Hello world!");
    });

    auto fut = prom.get_future();

    auto consumer = std::thread([&fut](){
        std::cout << "Consumer: waiting for a value to be available..." << std::endl;
        auto msg = fut.get();
        std::cout << "Consumer: received the value: " << msg << std::endl;
    });

    producer.join();
    consumer.join();

    return 0;
}
$ g++ -o test test.cpp
$ ./test 
Producer: waiting for a second...
Consumer: waiting for a value to be available...
Producer: setting the value...
Consumer: received the value: Hello world!

In the above example, we first created the promise object, prom, for a string value. Then the promise object is passed to the producer thread. The producer thread does not produce the value immediately, rather it starts waiting for a second (for demonstration purpose). We can confirm that from the output.

After starting the producer thread, we created a future object, fut, from the promise promauto fut = prom.get_future(). Now the promise-future duo (prom and fut) will share a common state.

The future object, fut, is then passed to the consumer thread which tries to retrieve a value immediately – auto msg = fut.get(). The fut.get() call will block until the producer thread meets the promise – sets the value to the promise object.

After waiting for a second, the producer thread sets value to the promise – prom.set_value(“Hello world!”);. The consumer thread immediately gets the value from the return of fut.get().

This way the produce and the consumer threads get synchronized perfectly. The consumer does not need to periodically poll or lose any time in the process of waiting for a value. As soon as the producer produces the value, the consumer gets notified immediately with that value.

Setting Exception

The producer might not be able to produce value all the time due to some error condition. In that case the producer can set an exception to the promise object instead of a value. The consumer will immediately be notified and an exception will be raised from the fut.get() call in the consumer thread. The exception can be caught and handled.

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

int main() {
    
    auto prom = std::promise<std::string>();

    auto producer = std::thread([&prom](){
        std::cout << "Producer: waiting for a second..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        try {
            /*Exception can be thrown in any error condition.*/
            throw std::runtime_error("runtime error");

            // if no error we can set the value.
            // prom.set_value("Hello world!");
        }
        catch(...) {
            std::cout << "Producer: setting the expection..." << std::endl;
            prom.set_exception(std::current_exception());
        }
    });

    auto fut = prom.get_future();

    auto consumer = std::thread([&fut](){
        std::cout << "Consumer: waiting for a value to be available..." << std::endl;
        try {
            auto msg = fut.get();
        }
        catch (std::runtime_error& e) {
            std::cout << "Consumer: exception received: " << e.what() << std::endl;
        }
    });

    producer.join();
    consumer.join();

    return 0;
}
$ ./test 
Producer: waiting for a second...
Consumer: waiting for a value to be available...
Producer: setting the expection...
Consumer: exception received: runtime error

What if the Promise is not Met

As the future::get() call blocks until the promise is set either with a value or an exception, there will be a problem if the producer never sets the promise or delays longer than the consumer can wait.

The consumer will be blocked forever if the promise is not set. The future::wait_for() is the rescue in this situation. We can specify in this function the maximum time the consumer can wait. This function will return if the promise is set or timeout happens – whichever happens first. The future::wait_for() will return the status whether the value is ready, timeout happened or the promise is deferred. Based of the status, the consumer can take actions.

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

int main() {
    
    auto prom = std::promise<std::string>();

    // no producer thread to set the promise.

    auto fut = prom.get_future();

    auto consumer = std::thread([&fut](){
            // Waiting for maximum 5 seconds...
            auto status = fut.wait_for(std::chrono::seconds(5));
            if (status == std::future_status::ready) {
                auto msg = fut.get();
            } if (status == std::future_status::timeout) {
                std::cout << "Consumer: future timeout happened." << std::endl;
            } else {
                std::cout << "Consumer: the promise is deferred." << std::endl;
            }
    });

    consumer.join();

    return 0;
}
$ ./test 
Consumer: future timeout happened.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

Your email address will not be published. Required fields are marked *

0
0
0
0
0
0