Boost logo

Boost :

Subject: Re: [boost] Common future base class (was Re: Boost.Fiber mini-review September 4-13)
From: Giovanni Piero Deretta (gpderetta_at_[hidden])
Date: 2015-09-04 20:04:46


On Fri, Sep 4, 2015 at 11:56 PM, Vicente J. Botet Escriba
<vicente.botet_at_[hidden]> wrote:
>>
> Hi Giovanni,
>
> boost::future has a lot of design issues. I will welcome and base class from
> which boost::future can inherit as it is quite difficult to maintain it by
> myself :(. I'm a little bit skeptical about the approach, but who knows.

what are your concerns in particular?

Basically what I would like is to decouple the signalling of a future
shared_state, as done by e.g. the promise, from the action to be
taken. The intuition is that most efficient implementations of
synchronization primitives are based on a fast lock free user space
signalling path plus a slow kernel path in case there are waiters. My
idea is to decouple the slow path from the actual signalling
implementation. The decoupling is done via an interface like this:

struct signalable {
   virtual void signal() = 0;
   atomic<signalable *> next = 0;
};

An actual signalable implementation could invoke a continuation
(unifying then and wait), signalling a condition variable, an event, a
file descriptor, switching to another fiber etc.

The signaler side has something like this:

struct signaler {
     void signal();

     bool try_wait(signalable *);
     bool try_unwait(signalable*);
};

Note this is not virtual. In fact it could simply be a concept.
Try_wait attempts to add the signalable to the wait list for the
signaler. Failure means that the signaler was signaled in the
meantime. try_unwait attempts to remove the signalable from the wait
list. Failure also means that the signaler was signaled in the
meantime.

The signaler can be implemented lock_free except in the case in which
try_unwait is called *and* there are multiple waiters. I believe this
case can be handled with a spinlock just for mutual exclusion between
unsignalers (try_waiters and signal can still be lock free), but I
have yet to implement it

With this interface a future can be implemented without any additional
synchronization.

For example the default implementation of 'then' would simply allocate
a new shared state for the returned future which inherit from
signalable and register it to the shared_state of the previous
continuation. The callback is allocated inline in the shared_state.
When signal is called, the callback is invoked.

The default wait of course would create a wait object on the stack (or
have a thread local one) which also implements the signalable
interface (a portable implementation would be based on a condition
variable + boolean flat + mutex). A timed wait need to use the
try_unwait interface if the clock times out.

Sorry for the rambling, hopefully the idea is a bit clearer.

I have shared my implementation previously. This is the link again:

https://github.com/gpderetta/libtask/blob/master/future.hpp

Note: incomplete, buggy and definitely not production ready.

> Note that boost::future and boost::thread have some interactions (at thead
> exit family functions) that need to be taken in account
>

I imagine that can be tricky. Is it much more complex than the thread
holding a pointer to the shared state to be made ready on exit?

-- gpd


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk