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:
- std::async with/std::future (std::shared_future) (when you want to execute on a one-time throw-away producer-thread async)
- std::packaged_task (when you want to assign a producer, but defer the call to the thread)
- 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:
std::async
through an asynchronous callback, which will return astd::future
instance.std::packaged_task
, which, when passed to a thread, will invoke its callback thereby updating thestd::future
instance associated with thatstd::packaged_task
. This mechanism allows for early binding of a producer, but a later invocation.std::promise
, which allows one to modify its associatedstd::future
through itsset_value()
call. With this direct control over mutating astd::future
, we must ensure that that the design is thread-safe if there are multiple producers (usestd::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:
- wait() will block on a given future until the result is produced
- 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() );
}