Boost logo

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