|
Boost : |
From: Johan Torp (johan.torp_at_[hidden])
Date: 2008-05-14 19:07:53
We've been discussing the need to implement a mechanism to wait for multiple
futures in a separate thread:
www.nabble.com/Review-Request%3A-future-library-%28Gaskill-version%29-to16600402.html
I've come up with some interfaces which I find intuitive and useful. I've
never implemented thread waiting, so please let me know if you think
something like this is viable.
------------------------------ Proposal ------------------------------
A future can be constructed and thereafter wait on four different "future
conditions"
* a promise
* a future_switch
* a future_barrier
* another future
Each condition has a specified return type and the futures you create from
it must match this type. They all have the same possible states - isn't
ready, has value or an expression. I have left these and alot of other
details out of the interface below for conciseness.
------------------------------ Interface drafts
------------------------------
/// Future condition which is ready when any of the added conditions is
ready. Has an arbitrary return-type
template<class ReturnType>
class future_switch
{
public:
/// Direct return
template<class FutureConditionWithMatchingReturnType>
void add_case(FutureConditionWithMatchingReturnType f);
/// Possibility to perform custom functor, bind state, perform some logic
etc before fulfilling it's condition
template<class FutureCondition, class FutureTypeToReturnTypeFunctor>
void add_case(FutureCondition f, FutureTypeToReturnTypeFunctor case);
/// Only valid in is_ready state. Removes last fulfilled futurecondition
and enables new futures to be
/// constructed. Existing futures will have copied the value or exception
already and will still work.
void remove_ready_case();
bool empty() const;
};
/// Future condition which is ready when all added futures are.
/// This interface is less promising and has some flaws. Maybe you can get
inspired by it though.
template<class ReturnType>
class future_barrier
{
public:
future_barrier(const function<ReturnType()>& onReady);
template<class FutureCondition>
void add_condition(const future<FutureType>& f);
};
------------------------------ Example usage
------------------------------
template<class T>
future_switch<T> operator||(const future<T>& lhs, const future<T>& rhs)
{
future_switch<T> sw;
sw.add_case(lhs);
sw.add_case(rhs);
return sw;
}
template<class T>
T and_impl(const future<T>& lhs, const future<T>& rhs)
{
return lhs.get() && rhs.get();
}
template<class T>
future_barrier<T> operator&&(const future<T>& lhs, const future<T>& rhs)
{
// Rather large runtime insecurity, we might forget to add lhs and rhs as
conditions to the barrier
future_barrier b(bind(&and_impl, lhs, rhs));
b.add_condition(lhs);
b.add_condition(rhs);
return b;
}
// This is an experimental use case for using switch as a dynamic set of
future conditions
// It handles three future responses and calls the three functions on the
next lines
bool handle_response_a(ResponseA r);
bool handle_response_b(ResponseB r);
bool handle_response_c(ResponseC r);
void handle_pending_requests(const future<ResponseA>& a,
const future<ResponseB >& b,
const future<ResponseC >& c)
{
future_switch<void> sw;
sw.add_case(a, &handle_repsonse_a);
sw.add_case(b, &handle_repsonse_b);
sw.add_case(c, &handle_repsonse_c);
bool all_ok = true;
while (!sw.empty())
{
// Will automatically remove ok:ed futures from sw
future<bool> next_response_was_ok(sw);
bool all_ok = all_ok && next_response_was_ok.get();
sw.remove_ready_case();
}
return all_ok;
}
------------------------------ Some comments
------------------------------
There is a lot to improve here. I'd appreciate it if we could focus on
determining whether this is a viable solution and the way we want to go
before diving into details.
These abstractions has the following property:
A Users can compose conditions and poll them without the need of waiting.
B We do not need to spawn extra threads for waiting - we only wait on the
"outmost" future
C Threads needn't awake and poll readiness - assuming the and/or logic is
handled by waitingcode.
D No "user logic" from the future-blocking threads propagate to the
promise-fulfilling thread.
E It's possible to implement efficient laziness by keeping readiness/waiting
logic internal in the future library
"wait for all" can be implemented without adding future_barrier, by simply
calling wait() on all futures. We lose property A and E for "wait for all"
if we do it this way. The blocking thread will awake unnecessarily many
times.
Property C means promise-fullfilling threads need to do some basic and/or
logic. I think this can be implemented in a safe and efficient manner,
O(log(N)) for barrier/all and O(1) for switch/any. I also think it is much
preferable to waking and switching to future-blocking threads which re-check
readiness conditions. Note property D.
Whatever mechanism we choose I think it's desirable to be able to easily
"lift" existing functions to futures;
R foo(A1 a1, A2 a2, A3 a3)
should be easily rewritable as;
future<R> foo(future<A1> a1, A2 a2, future<A3> a3) // Some parameters need
not be lifted
Johan
-- View this message in context: http://www.nabble.com/-future--Early-draft-of-wait-for-multiple-futures-interface-tp17242880p17242880.html Sent from the Boost - Dev mailing list archive at Nabble.com.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk