When is it a good idea to use std::promise over the other std::thread mechanisms?

kfmfe04 picture kfmfe04 · Jan 11, 2013 · Viewed 18.2k times · Source

I am trying to establish some heuristics to help me decide the appropriate std::thread class to use.

As I understand it, from highest level (simplest to use, but least flexible) to lowest level, we have:

  1. std::async with/std::future (std::shared_future) (when you want to execute on a one-time throw-away producer-thread async)
  2. std::packaged_task (when you want to assign a producer, but defer the call to the thread)
  3. std::promise (???)

I think I have a decent grasp of when to use the first two, but am still unclear about std::promise.

std::future in conjunction with a std::async call, effectively transforms a producing callback/functor/lambda to an asynchronous call (which returns immediately, by definition). A singular consumer can call std::future::get(), a blocking call, to get its results back.

std::shared_future is just a version which allows for multiple consumers.

If you want to bind a std::future value with a producer callback, but want to defer the actual invocation to a later time (when you associate the task to a spawning thread), std::packaged_task is the right choice. But now, since the corresponding std::future to the std::package_task could, in the general case, be accessed by multiple-threads, we may have to take care to use a std::mutex. Note that with std::async, in the first case, we don't have to worry about locking.

Having read some interesting links on promise, I think I understand its mechanisms and how to set them up, but my question is, when would you choose to use a promise over the other three?

I'm looking more for an application-level answer, like a rule-of-thumb (fill the ??? in 3. above), as opposed to the answer in the link (eg use std::promise to implement some library mechanism), so I can more easily explain how to choose the proper class to a beginning user of std::thread.

In other words, it would be nice to have an useful example of what I can do with a std::promise that cannot be done with the other mechanisms.

ANSWER

A std::future is a strange beast: in general, you cannot modify its value directly.

Three producers which can modify its value are:

  1. std::async through an asynchronous callback, which will return a std::future instance.
  2. std::packaged_task, which, when passed to a thread, will invoke its callback thereby updating the std::future instance associated with that std::packaged_task. This mechanism allows for early binding of a producer, but a later invocation.
  3. std::promise, which allows one to modify its associated std::future through its set_value() call. With this direct control over mutating a std::future, we must ensure that that the design is thread-safe if there are multiple producers (use std::mutex as necessitated).

I think SethCarnegie's answer:

An easy way to think of it is that you can either set a future by returning a value or by using a promise. future has no set method; that functionality is provided by promise.

helps clarify when to use a promise. But we have to keep in mind that a std::mutex may be necessary, as the promise might be accessible from different threads, depending on usage.

Also, David's Rodriguez's answer is also excellent:

The consumer end of the communication channel would use a std::future to consume the datum from the shared state, while the producer thread would use a std::promise to write to the shared state.

But as an alternative, why not simply just use a std::mutex on a stl container of results, and one thread or a threadpool of producers to act on the container? What does using std::promise, instead, buy me, besides some extra readability vs a stl container of results?

The control appears to be better in the std::promise version:

  1. wait() will block on a given future until the result is produced
  2. if there is only one producer thread, a mutex is not necessary

The following google-test passes both helgrind and drd, confirming that with a single producer, and with the use of wait(), a mutex is not needed.

TEST

static unsigned MapFunc( std::string const& str ) 
{ 
    if ( str=="one" ) return 1u; 
    if ( str=="two" ) return 2u; 
    return 0u;
}

TEST( Test_future, Try_promise )
{
    typedef std::map<std::string,std::promise<unsigned>>  MAP; 
    MAP          my_map;

    std::future<unsigned> f1 = my_map["one"].get_future();
    std::future<unsigned> f2 = my_map["two"].get_future();

    std::thread{ 
        [ ]( MAP& m )
        { 
            m["one"].set_value( MapFunc( "one" ));
            m["two"].set_value( MapFunc( "two" ));
        }, 
      std::ref( my_map ) 
    }.detach();

    f1.wait();
    f2.wait();

    EXPECT_EQ( 1u, f1.get() );
    EXPECT_EQ( 2u, f2.get() );
}

Answer

Seth Carnegie picture Seth Carnegie · Jan 11, 2013

You don't choose to use a promise instead of the others, you use a promise to fulfill a future in conjunction with the others. The code sample at cppreference.com gives an example of using all four:

#include <iostream>
#include <future>
#include <thread>
 
int main()
{
    // future from a packaged_task
    std::packaged_task<int()> task([](){ return 7; }); // wrap the function
    std::future<int> f1 = task.get_future();  // get a future
    std::thread(std::move(task)).detach(); // launch on a thread
 
    // future from an async()
    std::future<int> f2 = std::async(std::launch::async, [](){ return 8; });
 
    // future from a promise
    std::promise<int> p;
    std::future<int> f3 = p.get_future();
    std::thread( [](std::promise<int>& p){ p.set_value(9); }, 
                 std::ref(p) ).detach();
 
    std::cout << "Waiting...";
    f1.wait();
    f2.wait();
    f3.wait();
    std::cout << "Done!\nResults are: "
              << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
}

prints

Waiting...Done!

Results are: 7 8 9

Futures are used with all three threads to get their results, and a promise is used with the third one to fulfill a future by means other than a return value. Also, a single thread can fulfill multiple futures with different values via promises, which it can't do otherwise.

An easy way to think of it is that you can either set a future by returning a value or by using a promise. future has no set method; that functionality is provided by promise. You choose what you need based on what the situation allows.